什么是多线程?
线程是指程序运行的流程,多线程则是指可以运行一个以上线程的程序,多线程使程序运行的效率变得更高。
public class ThreadDemo {
public static void main(String[] args) {
new testThread().run();
for (int i = 0; i < 5; i++) {
System.out.println("main线程在运行");
}
}
}
class testThread {
public void run() {
for (int i = 0;i<5;i++) {
System.out.println("testThread 在运行");
}
}
}
以上代码可以看出,要想运行main方法里面的循环必需要等testThread方法里面的run方法执行完了才能运行,这便是单一线程的缺陷,执行效率低。要想多个线程同时运行那么就得在Java里激活多个线程。
通过继承Thread来创建线程
如何在Java里激活多个线程?
1. 线程必须扩展自Thread类,使自己成为他的子类。
2. 线程的处理必须编写在run()方法内。
public class ThreadDemo{
public static void main(String[] args) {
new ThreadTest().start();
for(int i = 10; i > 0; i--) {
System.out.println("线程名称:" + Thread.currentThread().getName() + ", "+i);
}
}
}
class ThreadTest extends Thread{
public void run() {
for(int i = 10; i > 0; i--) {
System.out.println("线程名称:" + Thread.currentThread().getName() + ", "+ i);
}
}
}
以上代码运行的结果为
线程名称:main, 10
线程名称:Thread-0, 10
线程名称:main, 9
线程名称:main, 8
线程名称:main, 7
线程名称:Thread-0, 9
线程名称:main, 6
线程名称:main, 5
线程名称:main, 4
线程名称:Thread-0, 8
线程名称:main, 3
线程名称:Thread-0, 7
线程名称:main, 2
线程名称:Thread-0, 6
线程名称:main, 1
线程名称:Thread-0, 5
线程名称:Thread-0, 4
线程名称:Thread-0, 3
线程名称:Thread-0, 2
线程名称:Thread-0, 1
上述结果发现两行输出的结果是没有顺序的,出现这样的结果是因为main方法也是一个线程。在java中所有的线程都是同时启动的,至于什么时候,哪个线程先执行,完全是由CPU决定的,这就得看谁先获得CPU资源了。实际上在命令行中运行Java命令时,就启动了一个JVM进程,默认情况下此进程会产生两个线程:一个是main方法线程,另外一个就是垃圾回收(GC)线程。
通过实现Runnable接口来创建线程
class 类名 implements Runnable{
@Override
public void run() {
····
}
}
警告⚠️
在执行线程的时候,不要是调用run方法,而应该调用Thread.start方法,因为直接调用run方法只会执行同一个线程中的任务,而不会启动新线程。调用Thread.start方法才会创建一个执行run方法的新线程。
Thread类和Runnable接口创建线程的比较
Thread类其实是实现了Runnable接口,也就是说Thread类是Runnable接口的一个子类,用Thread类无法达到资源共享的目的,如果实现了Runable接口的话,则很容易的实现资源共享。而就Runable接口相对于Thread类来说,有几个显著的优势:
1. 适合多个程序代码的线程去处理厅以资源的情况。
2. 避免Java的单继承特性带来的局限。因为Java程序只允许单一继承,一个子类只能有一个父类。
3. 增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。
线程状态
任何一种线程一般都有5种状态,即创建,就绪,运行,阻塞,终止。在给定时间点上,一个线程只能处于一种状态。要确定一个线程的当前状态,可调用getState方法。
(1). New:尚未启动的线程处于新创建
的状态,即新创建一个线程对象。
(2). Runnable:调用Thread.start方法的线程处于可运行
状态。在任何给定时刻,一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间。
(3). Blocked:受阻塞并等待某个监视器锁的线程处于被阻塞
状态。
(4). Waiting:无限期的等待另一个线程来执行某一特定操作的线程处于等待
状态。
(5). Timed waiting:等待另一个线程来执行取决于指定等待时间的操作线程处于计时等待
状态。
(6). Terminated:已退出的线程处于被终止
状态。线程有两原因被终止:
(1). 因为run方法正常退出而自然死亡。
(2). 因为一个没有捕获的异常终止了run方法而意外死亡。
线程操作的一些方法
操作线程的主要方法在Thread类中。以下列举了一些方法:
Thread.currentThread().getName():返回现在正在执行的线程的名称。
isAlive(): 判断一个线程是否已经启动而且仍然在启动,返回Boolean 类型。
join(): 等待线程终止。强制运行完调用join()方法的线程后再运行其他的线程。而其他线程处于阻塞状态,当运行完调用join()方法的线程后,其他线程变为可执行状态。
isInterrupted():判断目前线程是否被中断,返回Boolean类型。也可以使用interrupted()方法来判断当前线程是否被中断,但是interrupted()方法时候静态的方法,调用该方法会产生一个副作用,那就是将当前线程的中断状态重置为false。
setName():设置线程名称。
sleep(long millis): 使目前正在执行的线程休眠millis毫秒。线程转到阻塞状态,休眠时间结束之后,线程状态处于可运行状态。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法。此方法会抛出InterruptedException异常,用try...catch捕获。不会释放对象锁。
public class ThreadDemo{
public static void main(String[] args) {
ThreadTest te = new ThreadTest();
Thread t = new Thread(te);
System.out.println(t.getName()+"线程是否已经启动 "+t.isAlive());//判断线程是否已经启动,在start()方法之前线程未启动,在start()方法之后线程启动
t.start();//启动线程
int m = 0;
if(!Thread.currentThread().isInterrupted()) {//判断线程是否被中断
for(int i = 20; i > 0; i--) {
if(m == 5) {
try {
t.join();//强制运行完该线程再运行后面的线程
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
System.out.println("main线程名称:" + Thread.currentThread().getName() + ", "+i + ", m="+m++);//获取当前运行线程的名称
}
}
}
}
class ThreadTest implements Runnable{
private int tickets = 20 ;
@Override
public void run() {
for(int i = tickets; i > 0; i--) {
System.out.println("剩余演唱会门票: "+ i+ "张 线程名称"+Thread.currentThread().getName());
try {
Thread.sleep((int) (Math.random() * 10));//目前正在执行的线程休眠随机毫秒,当休眠的时间长一些,在打印的时候会明显看出来隔了一段时间才继续执行后面的线程。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
yiels():将目前正在执行的线程处于让步状态,把执行机会让给相同或者更高优先级的线程。这是一个静态方法。
setPriority(int newPriority):设置线程的优先级,默认为Thread.NORM_PRIORITY优先级,默认值为5。最小优先级和最大优先级的值分别为1和10。
setDaemon(Boolean isDaemon):标识该线程为守护线程或者用户线程,这一方法必须在线程启动之前调用。
不推荐使用方法
suspend():暂停线程 和 resume():恢复线程,这两种方法会导致死锁的发生,并且他们可以通过一个线程直接控制另外一个线程的代码来控制另外一个线程,所以这两种方法不推荐使用。
stop():停止线程,虽然stop能够避免死锁的发生,但是如果一个线程操作没有完成就被“stop”了,将会导致数据的不完整性。
sleep()和yield()的区别
sleep()使当前线程进入休眠状态,所有执行sleep()方法的线程进入了不可运行状态,在指定的时间内不会被执行;yield()使当前正在执行的线程处于让步状态,即重新回到可运行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。如果有其他的可运行的线程具有至少与此线程相同的优先级那么这些线程接下来会被调度。
线程同步
什么是线程同步?
要想解决数据共享问题,必须使用同步,所谓同步就是指多个线程在同一个时间段内只能有一个线程执行指定代码,其他线程要等待此线程完成之后才可以继续执行。可以将同步理解为“排队”
执行。
线程同步有两种方法:
1.同步方法:有synchronized关键字修饰的方法。当有一个线程进入了synchronized关键字修饰的方法,其他线程便不能进入,知道该线程执行该方法为止。同步方法实际上同步的是this 对象。
public synchronized void methodName(){
代码操作;
}
等同于
synchronized(this){
代码操作;
}
2.同步代码块:有synchronized关键字修饰的语句块。在同一时刻只能有一个线程进入同步代码块内进行运行,只有该线程离开同步代码块后,其他线程才能进入同步代码块内。
synchronized(任意对象){
代码操作;
}
e.g.:一般方法
package test;
public class SynchronizedForLock {
public static void main(String[] args) {
Emloyee emloyee = new Emloyee( "1", 1000);
new Thread(new OutMoney("琪琪",600,emloyee)).start();
new Thread(new OutMoney("岳岳",500,emloyee)).start();
new Thread(new OutMoney("琴琴",400,emloyee)).start();
}
}
class OutMoney implements Runnable {
private double outMoney;
private Emloyee emloyee;
private String name;
public OutMoney(String name,double outMoney, Emloyee emloyee) {
super();
this.outMoney = outMoney;
this.emloyee = emloyee;
this.name = name;
}
@Override
public void run() {
double liveMoney = emloyee.getAllMoney() - outMoney;
if(liveMoney >= 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
emloyee.setAllMoney(liveMoney);
System.out.println("线程名称:" + Thread.currentThread().getName() + " "+name+"取钱 ¥"+ outMoney+" 余额: ¥"+ liveMoney);
}else {
System.out.println("取钱失败,余额不足");
}
}
}
运行结果
线程名称:Thread-2 琴琴取钱 ¥400.0 余额: ¥600.0
线程名称:Thread-0 琪琪取钱 ¥600.0 余额: ¥400.0
线程名称:Thread-1 岳岳取钱 ¥500.0 余额: ¥500.0
由运行结果可以看出四个线程同时运行,但是由剩余的余额看出运行的总额数是1000,这是因为资源数据不同步引起的,这就需要线程同步来解决这个问题了。
方法一:同步代码块
package test;
public class SynchronizedForLock {
public static void main(String[] args) {
Emloyee emloyee = new Emloyee( "1", 1000);
new Thread(new OutMoney("琪琪",600,emloyee)).start();
new Thread(new OutMoney("岳岳",500,emloyee)).start();
new Thread(new OutMoney("琴琴",400,emloyee)).start();
}
}
class OutMoney implements Runnable {
private double outMoney;
private Emloyee emloyee;
private String name;
public OutMoney(String name,double outMoney, Emloyee emloyee) {
super();
this.outMoney = outMoney;
this.emloyee = emloyee;
this.name = name;
}
@Override
public void run() {
synchronized (emloyee) {//同步代码块
double liveMoney = emloyee.getAllMoney() - outMoney;
if(liveMoney >= 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
emloyee.setAllMoney(liveMoney);
System.out.println("线程名称:" + Thread.currentThread().getName() + " "+name+"取钱 ¥"+ outMoney+" 余额: ¥"+ liveMoney);
}else {
System.out.println("取钱失败,余额不足");
}
}
}
}
方法二:同步方法
public class SynchronizedForLock {
public static Object LOCK = new Object();
public static void main(String[] args) {
Emloyee emloyee = new Emloyee("1", 1000);
OutMoney outMoney0 = new OutMoney("琪琪", 600, emloyee);
OutMoney outMoney1 = new OutMoney("岳岳", 400, emloyee);
OutMoney outMoney2 = new OutMoney("琴琴", 500, emloyee);
new Thread(outMoney0).start();
new Thread(outMoney1).start();
new Thread(outMoney2).start();
}
static class OutMoney implements Runnable {
private double outMoney;
private Emloyee emloyee;
private String name;
public OutMoney(String name, double outMoney, Emloyee emloyee) {
super();
this.outMoney = outMoney;
this.emloyee = emloyee;
this.name = name;
}
@Override
public void run() {
emloyee.outMoneySy(outMoney,name);//同步方法
}
}
Emloyee类
public class Emloyee {
private String num;
private String name ;
private double allMoney;
public Emloyee(String num, double allMoney) {
super();
this.num = num;
this.allMoney = allMoney;
}
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getAllMoney() {
return allMoney;
}
public void setAllMoney(double liveMoney) {
this.allMoney = liveMoney;
}
public synchronized void outMoneySy(double outMoney,String name) {
double liveMoney = allMoney - outMoney;
if (liveMoney >= 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
allMoney = liveMoney;
System.out.println("线程名称:" + Thread.currentThread().getName() + " " + name + "取钱 ¥" + outMoney+ " 余额: ¥" + liveMoney);
} else {
System.out.println("取钱失败,余额不足");
}
}
}
运行结果
线程名称:Thread-0 琪琪取钱 ¥600.0 余额: ¥400.0
取钱失败,余额不足
线程名称:Thread-1 岳岳取钱 ¥400.0 余额: ¥0.0
同步代码块有synchronized (this)用法,还有synchronized (非this对象)【例如上面同步代码块例子】以及synchronized (类.class)这两种用法,而使用不同的同步代码块的方法,它们的含义也是不同的。
synchronized修饰非静态方法时,同步代码块的synchronized (this)和synchronized (非this对象)的锁是对象,(this指的是什么呢?它指的就是调用这个方法的对象)线程要执行对应同步代码,需要获得相应的对象锁。而同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得相应的类锁。
synchronized修饰静态方法时,同步方法只能使用类锁和静态对象,而非静态同步方法可以是类锁或者任何对象。因为在加载类文件的时候,静态同步方法由于是静态的也被加载进内存了,类名.class的加载优先级高于静态方法。
有synchronized关键字修饰的方法或者代码块来实现加锁,并且同步的范围越大,性能就越差。
Java是通过object类的wait(),notify(),notityAll ()这几个方法来实现线程之间的通信的,而所有的类都是从object继承的所以任何类都可以直接使用这些方法。并且这些方法只能在synchronized方法中调用。
(1) . wait():使一个线程处于等待状态,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒线程,调用wait()方法的当前线程会释放对该同步监视器(锁)的锁定。
(2) . notify():唤醒一个处于等待状态的某一个线程,注意的是在调用此方法的时候,并不能确切的
唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是由线程的优先级决定。
(3) . notityAll ():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,
而是让它们竞争。具有最高优先级的线程最先被唤醒并执行。
e.g.
package test;
public class SynchronizedForLock {
public static void main(String[] args) throws InterruptedException {
Emloyee emloyee = new Emloyee("1", 1000);
OutMoney outMoney0 = new OutMoney("琪琪", 600, emloyee);
OutMoney outMoney1 = new OutMoney("岳岳", 400, emloyee);
OutMoney outMoney2 = new OutMoney("琴琴", 500, emloyee);
OutMoney none = new OutMoney(null, -1, emloyee);
new Thread(outMoney0,"#1---").start();
new Thread(outMoney1,"#2---").start();
new Thread(outMoney2,"#3---").start();
Thread.sleep(2000);
new Thread(none,"#4---").start();
}
static class OutMoney implements Runnable {
private double outMoney;
private Emloyee emloyee;
private String name;
public OutMoney(String name, double outMoney, Emloyee emloyee) {
super();
this.outMoney = outMoney;
this.emloyee = emloyee;
this.name = name;
}
@Override
public void run() {
emloyee.outMoneySy(outMoney,name);//同步方法
}
}
}
Emloyee类
package test;
public class Emloyee {
private String num;
private String name ;
private double allMoney;
public Emloyee(String num, double allMoney) {
super();
this.num = num;
this.allMoney = allMoney;
}
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getAllMoney() {
return allMoney;
}
public void setAllMoney(double liveMoney) {
this.allMoney = liveMoney;
}
// 同步方法
Boolean threadState = true;
public synchronized void outMoneySy(double outMoney,String name){
if(name == null)
{
notifyAll();
return ;
}
double liveMoney = allMoney - outMoney;
if(liveMoney >= 0) {
while(!threadState) {
try {
System.out.println("线程名称:" + Thread.currentThread().getName() + "被等待 ");
this.wait();
threadState = true ;
System.out.println("线程名称:" + Thread.currentThread().getName() + "在wait后执行了该方法");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
allMoney = liveMoney;
System.out.println("线程名称:" + Thread.currentThread().getName() + " " + name + "取钱 ¥" + outMoney+ " 余额: ¥" + liveMoney);
} else {
System.out.println("线程名称:" + Thread.currentThread().getName() + "取钱失败,余额不足");
}
threadState = false;
this.notify();
System.out.println("线程名称:" + Thread.currentThread().getName() + "被唤醒 " + "threadState = " + threadState);
}
}
运行结果
线程名称:#1--- 琪琪取钱 ¥600.0 余额: ¥400.0
线程名称:#1---被唤醒 threadState = false
线程名称:#3---取钱失败,余额不足
线程名称:#3---被唤醒 threadState = false
线程名称:#2---被等待
线程名称:#2---在wait后执行了该方法
线程名称:#2--- 岳岳取钱 ¥400.0 余额: ¥0.0
线程名称:#2---被唤醒 threadState = false
为什么线程处于等待状态时要用while 循环?
因为当线程被等待后唤醒依然会执行while的循环条件,但是if将不会执行。
什么是条件对象?
线程进入临界区,却发现在某一条件满足之后它才能执行,需要使用一个条件对象来管理那些已经获得了锁但是却不能做有用工作的线程。例如工厂生产产品和商店卖出产品,但是商店的库存小于要购买的产品的数量,这时必须通过工厂再生产产品才能继续支持购买操作。当工厂在执行生产操作时,条件对象就用来管理商店卖商品的操作。
创建一个与该锁相关的条件对象:Condition newCondition()。
condition 方法必须被命名为await,signal,signalAll,以便它们不被某些方法冲突。
await():将该线程放到条件的等待集中,直到线程从等待集中移出或等待了指定时间后才能解除阻塞。
signalAll():解除该条件的等待集中的所有线程的阻塞状态。
signal():从该条件的等待集中随机的选择一个线程,解除其阻塞状态。
调用wait()/notifyAll()等价于调用condition.await()/condition.signalAll();
e.g.
//A_Producter 类
package test;
public class A_Producter {
private String productName ;//生产者名称
private int shirtNum;//T恤生产件数
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public int getShirtNum() {
return shirtNum;
}
public void setShirtNum(int shirtNum) {
this.shirtNum = shirtNum;
}
public A_Producter(String productName, int shirtNum) {
this.productName = productName;
this.shirtNum = shirtNum;
}
}
//A_Consumer类
package test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class A_Consumer {
private String consumerName;//购买人姓名
private int shirtNum;//购买件数
private A_Producter a_Producter;
public String getConsumerName() {
return consumerName;
}
public void setConsumerName(String consumerName) {
this.consumerName = consumerName;
}
public int getShirtNum() {
return shirtNum;
}
public void setShirtNum(int shirtNum) {
this.shirtNum = shirtNum;
}
public A_Consumer(A_Producter a_Producter) {
this.a_Producter = a_Producter;
}
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();//创建一个与该锁相关的条件对象
int overplusShirt;
public void consumer(String consumerName , int shirtNum) {
lock.lock();//锁定
try{
//如果购买的T恤总量大于生产的T恤或者大于剩余的T恤总量,则进行await加入等待集
while( a_Producter.getShirtNum() < shirtNum) {
try {
condition.await();
System.out.println("调用await方法");
} catch (InterruptedException e) {}
}
if(a_Producter.getShirtNum() >= shirtNum){
overplusShirt = a_Producter.getShirtNum() - shirtNum;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
a_Producter.setShirtNum(overplusShirt);
System.out.println(consumerName +"购买了 "+ shirtNum + " 件,剩余T恤 "+ overplusShirt +" 件");
}
}finally {
lock.unlock();//解锁
}
}
//生产T恤
public void product(String productName ,int producttNum) {
lock.lock();//锁定
try{
if( producttNum > 0) {
overplusShirt = overplusShirt+producttNum;
a_Producter.setShirtNum(overplusShirt);
System.out.println(productName + "生产了 "+ producttNum +"件T恤,剩余 "+ overplusShirt + " 件T恤");
condition.signalAll();
}
}finally {
lock.unlock();//解锁
}
}
}
//
package test;
public class A_ConditionObjTest {
public static void main(String[] args) {
A_Producter a_Producter = new A_Producter("丫妹儿", 15);
A_Consumer a_Consumer = new A_Consumer(a_Producter);
buyShirt qiqi = new buyShirt("琪琪", 5,a_Consumer);
buyShirt yueyue = new buyShirt("岳岳", 4,a_Consumer);
buyShirt qinqin = new buyShirt("琴琴", 7, a_Consumer);
buyShirt fengfeng = new buyShirt("凤凤", 11, a_Consumer);
new Thread(qiqi).start();//购买T恤
new Thread(yueyue).start();
new Thread(qinqin).start();
new Thread(fengfeng).start();
//生产T恤
new Thread(new Runnable() {
public void run() {
a_Consumer.product("睿睿", 10);
}
}).start();
new Thread(new Runnable() {
public void run() {
a_Consumer.product("康康", 20);
}
}).start();
}
static class buyShirt implements Runnable{
private String consumerName;
private int shirtNum;
private A_Consumer a_Consumer;
public buyShirt(String consumerName, int shirtNum, A_Consumer a_Consumer) {
super();
this.consumerName = consumerName;
this.shirtNum = shirtNum;
this.a_Consumer = a_Consumer;
}
@Override
public void run() {
a_Consumer.consumer(consumerName, shirtNum);
}
}
}
运行结果
琪琪购买了 5 件,剩余T恤 10 件
岳岳购买了 4 件,剩余T恤 6 件
睿睿生产了 10件T恤,剩余 16 件T恤
康康生产了 20件T恤,剩余 36 件T恤
调用await方法
琴琴购买了 7 件,剩余T恤 29 件
调用await方法
凤凤购买了 11 件,剩余T恤 18 件
一个锁对象可以拥有一个或者多个相关的条件对象,可以通过newCondition方法来获取一个条件对象。
通常,对await的调用应该在如下形式的循环体中
while(!(ok to proceed){
condition.await();
}
锁对象
每个对象都仅有一个锁,如果两个线程访问不同的对象,那么每一个线程将获得不同的锁对象,因此两个线程都不会发生阻塞。如果两个线程访问同一个对象,那么锁以串行的方式提供服务。线程一旦获取了该对象的锁,其他访问该对象的线程则无法再访问该对象的其他非同步方法。
使用锁时,一定要注意标记为全局变量,如果标记为局部变量,每个线程再使用的时候都会创建一个新的锁对象,这样加锁就没有任何意义了。
锁分为对象锁、可重入锁,公平锁,读写锁等。
可重锁 ReentrantLock
可重入锁在执行对象中所有同步方法不用再次获得锁。
公平锁
ReentrantLock(boolean fair) 构建一个带有公平策略的锁。一个公平锁偏爱等待时间最长的线程,但这一公平的保证大大降低性能,所以在默认的情况下,锁没有被限制为公平。即使使用公平锁,也无法保证线程调度是公平的。
读写锁 ReentrantReadWriteLock
读写锁把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。一个读写锁同时只能有一个写者或多个读者,但不能同时既有读者又有写者。读写锁适用于多个线程从一个数据结构中读取数据而很少修改其中数据的环境。
ReentrantReadWriteLock 主要有两种方法:
Lock readLock():得到一个可以被多个读操作共用的读锁,但会排斥所有的写操作。
Lock writeLock():得到一个写锁,排斥所有其他的读操作和写操作。
读写操作必要步骤:
//构建ReentrantReadWriteLock 对象
private ReentrantReadWriteLock writeAndread = new ReentrantReadWriteLock();
//获取读锁和写锁
private Lock readLock = writeAndread.readLock();
private Lock writeLock = writeAndread.writeLock();
Lock接口中常用的方法有:
1. lock():获得锁,如果锁同时被另一个线程拥有,则发生阻塞。
2. unlock():释放这个锁。
3. tryLock():尝试获得锁而没有发生阻塞,则返回true。
4. trylock(long time, TimeUnit unit):尝试获得锁,在给定时间内不会发生阻塞,返回true。
5. lockInterruptibly():获得锁,但是会不确定地发生阻塞。如果线程被中断,则抛出InterruptedException异常。
private Lock lock = new ReentrantLock();//可重入锁
public void outMoneySy(double outMoney,String name) {
if(lock.tryLock()) {//尝试获取锁,也可以使用trylock(long time, TimeUnit unit)来尝试获取锁,如果超出给定时间还得不到锁,则返回false
lock.lock();
try {
double liveMoney = allMoney - outMoney;
if (liveMoney >= 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
allMoney = liveMoney;
System.out.println("线程名称:" + Thread.currentThread().getName() + " " + name + "取钱 ¥" + outMoney+ " 余额: ¥" + liveMoney);
} else {
System.out.println("取钱失败,余额不足");
}
} finally {
lock.unlock();
System.out.println("线程名称:" + Thread.currentThread().getName() + "释放锁 ");
}
}else {
System.out.println("线程名称:" + Thread.currentThread().getName() + "正在占用锁 ");
}
}
必须把解锁操作放在finally语句之内,如果在临界区的代码抛出异常,锁必须被释放,否则,其他线程将永远被阻塞。线程每一次调用lock()都必须要调用unlock()来解锁。
因为Java中每一个对象都有一个内部锁,所以
public synchronized void methodName(){
代码操作;
}
等价于
public void methodName() {
this.锁对象.lock();//锁定
try {
代码操作;
} finally {
this.锁对象.unlock();//解锁
}
}
死锁
什么是死锁?死锁是怎么形成的?
当两个线程互相等待对方释放同步监视器时就会发生死锁。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于堵塞状态,无法继续。如果同步中嵌套其他同步,程序出现无限等待,也可能会产生死锁。系统资源的竞争和进程请求和释放资源的顺序不得当将会形成死锁。
synchronzied(A锁){
synchronized(B锁){
代码操作;
}
}
e.g.
package test;
public class SynchronizedForLock {
static Object lock1 = new Object() ;
static Object lock2 = new Object();
public static void main(String[] args) throws InterruptedException {
OutMoney out1 = new OutMoney();
OutMoney out2 = new OutMoney();
out1.flag = 1;
out2.flag = 0;
new Thread(out1).start();
new Thread(out2).start();
}
static class OutMoney implements Runnable {
public int flag = 1;
public OutMoney() {}
@Override
public void run() {
System.out.println("flag=" + flag);
if (flag == 1) {
synchronized (lock1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (lock2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("0");
}
}
}
}
}
}
程序运行结果
flag=0
flag=1
由运行结果看出,flag等于1时,同步obj1休眠了500毫秒,obj1被锁定了,当flag等于0 时,同步obj2休眠了500毫秒,obj2被锁定了.当obj1休眠结束后,需要锁定obj2才能执行,但是obj2已经被锁定而未解锁。当obj2休眠结束后,需要锁定obj1才能执行,但是obj1已经被锁定。此时,obj1,obj2相互等待对方解锁,从而出现死锁现象。如果使用debug的方式运行程序,运行的结果可能和使用一般方式运行的结果不相同,那是因为使用debug方式运行时候,所打的断点会影响程序的运行。(个人理解打断点相当于使线程进行了休眠,从而产生不同的运行结果)
如何避免死锁的产生?
1 . 在线程获取锁的时候给锁加上一个时限,如果超过该时限就放弃对锁的请求,并且释放自己占有的锁。可以使用Lock类中的tryLock方法来尝试获得锁,如果没有发生阻塞则返回true;
2 . 对死锁进行检测,如果检测出死锁,则释放所有的锁,并且中断线程或者回退。
3 .给锁加上顺序,让线程按照顺序来获得锁。尽量减少嵌套。
synchronized的缺陷:当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题。
Lock 与Synchronized的区别
类型 | Synchronized | Lock |
---|---|---|
存在层次 | synchronized 是java中的关键字,在jvm层实现 | Lock是一个接口, java.util.concurrent.locks |
锁的释放 | 获取锁的线程执行完同步代码释锁;线程发生异常,会自动释放线程占有的锁 | 一般是在try{}finally{}结构中,解锁操作必须放在finally语句之中,不然容易发生死锁 |
锁的获取 | 如果A线程获得了锁,则B线程会等待;如果A线程被阻塞了,则B线程将会永远等待 | 如果使用Lock.lock()方法的话,需要进行解锁Lock.unlock()操作;如果使用tryLock()方法来尝试获得锁,那么其他线程将不用等待,可以先去执行其他的方法 |
锁的状态 | 无法获取锁的状态 | 可以通过tryLock()方法来获取锁的状态,如果返回true,说明线程没有发生阻塞 |
性能 | 少量同步 | 大量同步,性能要远远优于synchronized(当并发量小的时候,性能和Synchronized区别不大) |
线程池
什么是线程池?线程池有什么作用?
就线程池的名称来说,顾名思义,线程池就是一个存放多个线程的容器。一个线程池中包含了很多准备运行的空闲线程。将runnable对象交给线程池就会有一个线程调用run方法,当run方法退出时,线程不会死亡,而是在线程池中准备为下一个请求提供服务,省去了不断创建和销毁线程的过程,避免出现系统资源消耗过多的情况。创建和销毁线程以及正在运行中的线程都会消耗非常多的系统资源从而导致系统资源不足。为了避免不断系统资源不足的情况出现,使用线程池来存放多次使用额线程来使程序响应更快。
Callable与Future
Callable与Runnable类似,但是有返回值:如Callable<Integer>,Callable接口只有一个call方法,用于运行一个将产生结果的任务。
Future用于保存异步计算的结果。
Future接口具有以下几种方法:
get(long time,TimeUnit unit):获取结果,如果没有结果可用,则阻塞直到超过制定的时间为止,如果不成功,第二个方法将会抛出TimeoutException异常。
cancel(boolean mayinterruput):尝试取消这一任务,如果任务已经开始,并且参数值为true,它就会被中断,如果成功执行了取消操作,则返回true。
isCancelled():如果任务在完成之前被取消了,则返回true。
isDone():如果任务结束,无论是正常结束,还是被中断,取消或发生异常,都将返回true。
如何创建和使用线程池?
执行器(executor)中有很多静态工厂的方法用来构建线程池:
方法 | 描述 |
---|---|
newCachedThreadPool | 创建新线程,必要时线程会被保留60秒 |
newFixedThreadPool | 该池包含固定数量的线程,空闲线程会被保留 |
newScheduledThreadPool | 用于预定执行而构建的固定线程池。 |
newSingleThreadExecutor | 只有一个线程的池,该线程顺序执行没有个提交的任务。 |
newSingleThreadScheduledExecutor | 用于预定执行而构建的单线程“池”。 |
使用线程池中的线程的步骤
- 创建线程池。
- 创建Runnable接口对象或者Callable接口对象
- 提交Runnable接口对象或者Callable接口对象。调用submit方法提交。
- 关闭线程。ThreadPoolExecutor提供了两个方法来关闭线程池,一个是shutdown() ,一个是shutdownNow()。
shutdown():不会立即终止线程,当所有任务都完成之后,线程池中的线程死亡。
shutdownNow():立即终止线程,取消尚未开始的所有任务并试图中断正在运行的线程。
e.g.
Callable接口对象
package threadPool;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);//创建线程池对象,其中包含两个线程对象,如果线程数超过预设线程则等待。
ThreadPool threadPool = new ThreadPool();
pool.submit(threadPool);
Future<String> result = pool.submit(threadPool);//提交Callable接口对象
try {
String backResult = result.get();//获取返回结果
System.out.println("打印返回结果 : "+ backResult);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//关闭线程
pool.shutdown();
}
}
//创建一个Callable接口对象,其返回值为String
class ThreadPool implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("这是一个测试线程池");
return "哈哈哈,这是Callable返回的结果";
}
}
运行结果
这是一个测试线程池
这是一个测试线程池
打印返回结果 : 哈哈哈,这是Callable返回的结果
总结不太好,有错请及时指出 !!!