目录
普通方法调用和多线程区别
程序,进程-process,线程-Thread
创建线程方式一:继承Thread类
创建线程方式二:实现Runnable接口
初识并发问题
Lambda表达式
线程五种状态
停止线程
线程休眠
线程等待--wait
线程礼让---yield
线程强制执行----join
观测线程状态
线程的优先级---priority
守护线程---daemon
线程同步机制
三大线程不安全案例
同步方法及同步块
CopyOnWriteArrayList
死锁
Lock锁
生产者消费者问题
管程法
信号灯法
线程池
四种线程池介绍
缓冲队列BlockingQueue和自定义线程池ThreadPoolExecutor
在操作系统中运行的程序就是进程,比如QQ,播放器,游戏,IDE等,一个进程可以有多个线程,比如视频中同时听声音,看图像,看弹幕等等。
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念;
进程则是执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位;一个进程至少有一个线程,不然没有存在的意义;
线程是CPU调度和执行的单位。
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。线程之间切换是因为每个线程都有一个时间片,时间片到期就会cpu被另一个线程抢占使用,也就是cpu允许一个线程运行多长时间,单位好像是纳秒。
总结:线程就是独立的执行路径;在程序运行时,即使没有自己创建线程,后台也会又多个线程,如子线程,gc线程,也称之为守护线程,是jvm提供的;main()称之为主线程,为系统的入口,用于执行整个程序;在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的;对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;线程会带来额外的开销,如cpu调度时间、并发控制开销;每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
创建线程方式:继承Thread类,重写run()方法,调用start开启线程
总结:注意,线程开启不一定立即执行,由cpu调度执行
public class Demo1 extends Thread{
public void run(){
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我是run方法"+i);
}
}
public static void main(String[] args) {
//创建一个线程对象
Demo1 demo1 = new Demo1();
//调用start()方法开启线程
demo1.start();
//main线程,主线程
for (int i = 0; i < 1000; i++) {
System.out.println("我是主线程"+i);
}
}
}
//结果其中一块是这样,因为由于单核cpu,所以线程是交替执行的,线程在争抢时间片
/*
我是run方法2
我是主线程32
我是主线程33
我是主线程34
我是主线程35
我是run方法3
我是run方法4
我是run方法5
我是主线程36
*/
继续,如果按照下方的多线程写法呢,同时执行这个类里的方法。
public class Demo1 extends Thread{
private String name;
public Demo1(String name){
this.name=name;
}
public void run(){
//run方法线程体
System.out.println(name);
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1("1");
Demo1 demo2 = new Demo1("2");
Demo1 demo3 = new Demo1("3");
demo1.start();
demo2.start();
demo3.start();
}
}
/*
1
3
2
*/
每次结果都不唯一,很明显,也没有按照预计的要求顺序输出,说明是同步执行。
代理:就是你把东西给我,我帮你执行
public class Demo2 implements Runnable{
public void run(){
//run方法线程体
for (int i = 0; i < 5; i++) {
System.out.println("线程编号"+i);
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
Demo2 demo2 = new Demo2();
//创建线程对象,通过线程对象来开启我们的线程,这就是线程代理
new Thread(demo2).start(); //把对象代理给线程使用
for (int i = 0; i < 10; i++) {
System.out.println("主线程编号"+i);
}
}
}
总结:
继承Thread类
实现Runnable接口
StartThread st=new StartThread(); //一份资源
new Thread(st,"小明").start();
new Thread(st,"小黑").start(); //多个代理,st后面的是线程的名字
new Thread(st,"小白").start();
多个线程抢票,创造一个并发问题
多个线程同时操作同一个对象,线程不安全,数据紊乱,如下方输出结果5,6两个票被多个人同时拿到,这就是并发问题。
public class Demo3 implements Runnable{
private int ticketnums=10;
public void run(){
while (true){
if(ticketnums==0){
break;
}
//模拟延时,放置cpu执行过快看不到超卖现象
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢了第"+ticketnums--+"个票");
}
}
public static void main(String[] args) {
Demo3 demo3 = new Demo3();
new Thread(demo3,"小明").start();
new Thread(demo3,"小白").start();
new Thread(demo3,"黄牛").start();
}
}
/*
小白抢了第10个票
黄牛抢了第9个票
小明抢了第8个票
小白抢了第6个票
小明抢了第7个票
黄牛抢了第6个票
小明抢了第5个票
黄牛抢了第4个票
小白抢了第5个票
黄牛抢了第3个票
小白抢了第2个票
小明抢了第1个票
*/
模拟龟兔赛跑,这里不让兔子休眠了
//模拟龟兔赛跑
public class Race implements Runnable{
//定义比赛跑到长100米
public static String winnder;
public void run(){
for (int i = 0; i <= 100 ; i++) {
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
boolean over = getWinnder(i);
if (over){ //为true比賽結束
break;
}
}
}
//判断比赛是否结束并输出赢家
public boolean getWinnder(int steps){
if(steps==100){
winnder=Thread.currentThread().getName();
System.out.println("胜利者是"+winnder);
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
new Thread( ()-> System.out.println("Hello world") ).start();
为什么要使用lambda表达式
理解Functional Interface(函数式接口)是学习Java8 lambda表达式的关键所在
函数式接口的定义:接口里的方法默认是抽象的,是public abstract修饰的,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
例如:public interface Runnable{
void run(); //只有一个方法
}
对于函数式接口,可以通过lambda表达式来创建该接口的对象,说白了就是为了简化一个实现类,不用再定义内部类了。
public class Lambda {
public static void main(String[] args) {
// ILike like = new Like();
// like.lambda();
ILike like;
//匿名内部类,没有类的名称,必须借助接口或者父类
like = new ILike(){
@Override
public void lambda() {
System.out.println("I like lambda2");
}
};
like.lambda();
//使用lambda方式实现,根本不需要再去定义一个外部类或者匿名内部类
like= () -> {
System.out.println("I like lambda3");
};
like.lambda();
}
}
//定义函数式接口
interface ILike{
void lambda();
}
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda");
}
}
在来一例有参数的,Lambda表达式只有在只有一行代码的情况下才能简化成一行代码,多行就用{}包裹。
public class Application {
public static void main(String[] args) {
ILove iLove;
iLove = (int a) -> {
System.out.println("I love you "+a+"天");
System.out.println("I love you "+a+"天");
};
iLove.love(520);
//简化
iLove=(a) -> {
System.out.println(("I love you "+a+"天"));
};
iLove.love(521);
//再简化
iLove=a -> {
System.out.println(("I love you "+a+"天"));
};
iLove.love(522);
//最后一次简化
iLove=a -> System.out.println(("I love you "+a+"天"));
iLove.love(523);
}
interface ILove{
void love(int a);
}
}
创建状态——就绪状态——阻塞状态——运行状态——死亡状态
public class Demo4 implements Runnable{
private boolean flag = true; //设置一个标志位
@Override
public void run() {
int i=0;
while (flag){
System.out.println("run...Thread"+i++);
}
}
//自定义一个公开的方法停止线程,转换标志位
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
Demo4 demo4 = new Demo4();
new Thread(demo4).start();
for (int i = 0; i < 1000; i++) { //快捷键:1000.fori
System.out.println("主线程"+i);
if (i==900){
demo4.stop(); //调用stop方法切换标志位,让线程停止
System.out.println("线程该停止了");
}
}
}
}
//模拟倒计时
public class Demo5 {
public static void main(String[] args) {
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void tenDown() throws InterruptedException{
int num=10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if(num<=0){
break;
}
}
}
}
wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)
wait(long timeout)让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
wait必须用在同步代码块里.
// main(主线程)
synchronized(t1) {
try {
t1.start();
t1.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
// 在 t1 线程中唤醒主线程
synchronized (this) { //这里的 this 为 t1
this.notify();
}
public class Demo6 {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"小明").start();
new Thread(myYield,"小黑").start();
}
static class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield(); //礼让,但不一定成功
System.out.println(Thread.currentThread().getName()+"线程执行结束");
}
}
}
/*
礼让成功的输出:
小黑线程开始执行
小明线程开始执行
小黑线程执行结束
小明线程执行结束
礼让失败的输出:
小明线程开始执行
小明线程执行结束
小黑线程开始执行
小黑线程执行结束
*/
如下,结果当i=50的时候会阻塞主线程,然后执行插队的线程,插队线程执行完毕后再执行主线程
public class Demo7 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我是来插队的"+i);
}
}
public static void main(String[] args) throws InterruptedException {
Demo7 demo7 = new Demo7();
Thread thread = new Thread(demo7);
thread.start();
for (int i = 0; i < 100; i++) {
if (i == 50) {
thread.join();
}
System.out.println("我是主线程" + i);
}
}
}
/*
我是主线程49
我是来插队的0
我是来插队的1
我是来插队的2
.....
我是来插队的99
我是主线程50
*/
Thread.State state = thread.getState();
System.out.println(state);
public class Demo8 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
for (int i = 0; i < 5; i++) { //先睡5秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("||||||||");
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state); //NEW
//观察启动后
thread.start();
state=thread.getState();
System.out.println(state); //Run
while (state!=Thread.State.TERMINATED){ //只要线程不终止就一直输出状态
Thread.sleep(100);
state=thread.getState(); //更新线程状态
System.out.println(state); //输出状态
}
}
}
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程, 线程调度器按照优先级决定应该调度哪个线程来执行。但是优先级高的不一定先执行,大多数情况下会先执行,与那个线程礼让yield一样的道理。优先级默认是5。优先级低只是意味着获得调度的概率低.并不是优先级低就不会被调用了.这都是看CPU的调度。
线程的优先级用数字表示,范围从1~10.
使用以下方式改变或获取优先级 getPriority() ,setPriority(int xxx)
public class Demo9 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程优先级"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
t1.start(); //先设置优先级在启动
t2.setPriority(Thread.MIN_PRIORITY); //优先级最低,为1
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY); //优先级最高,为10
t4.start();
// t5.setPriority(-1); //不可为负数,会报错
// t5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"子线程优先级"+Thread.currentThread().getPriority());
}
}
/*
main主线程优先级5
Thread-3子线程优先级10
Thread-0子线程优先级5
Thread-2子线程优先级4
Thread-1子线程优先级1
*/
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
下方代码执行结果说明了在用户线程执行完毕后,到JVM关闭的时候,这段时间守护线程还会跑一会才结束。
public class Demo10 {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread=new Thread(god);
thread.setDaemon(true); //默认是false表示为用户线程,正常的线程都是用户线程
thread.start(); //守护线程启动
/*
上帝守护线程启动,注意 thread.setDaemon(true)必须在thread.start()之前设置,
否则会跑出一个IllegalThreadStateException异常。
因为你不能把正在运行的常规线程设置为守护线程。
*/
new Thread(you).start(); //启动用户线程
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保佑着你");
}
}
}
//你
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("开心的活了一生");
}
System.out.println("===GoodBye! world!===");
}
}
这里有几点需要注意:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。
因为你不可能知道在所有的User完成之前,Daemon是否已经完成了预期的服务任务。一旦User退出了,可能大量数据还没有来得及读入或写出,计算任务也可能多次运行结果不一样。这对程序是毁灭性的。造成这个结果理由已经说过了:一旦所有User Thread离开了,虚拟机也就退出运行了。
多个线程操作一个资源,即并发,线程同步需要队列+锁这两个东西来解决线程不安全问题。
要保证安全就会牺牲部分性能,要保证性能就要牺牲部分安全。所谓鱼与熊掌不可兼得就是这个道理。
处理多线程问题时,多个线程访问同一一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。
线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
◆由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized ,当-个线程获得对象的排它锁,独占资源, 其他线程必须等待,使用后释放锁即可.存在以下问题:
1)不安全的买票
public class Demo1 {
public static void main(String[] args) {
BuyTickets buyTickets = new BuyTickets();
new Thread(buyTickets,"我").start();
new Thread(buyTickets,"你们").start();
new Thread(buyTickets,"黄牛").start();
}
}
class BuyTickets implements Runnable{
private int ticketName=10;
boolean flag = true;
@Override
public void run() {
//买票
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
//判断是否有票
if(ticketName<=0){
flag=false;
return;
}
//模拟延时
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"拿到"+ticketName--);
}
}
2)两个人去银行取钱
public class Demo2 {
public static void main(String[] args) {
//账户
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account,50,"自己");
Drawing wife = new Drawing(account,100,"老婆");
new Thread(you).start();
new Thread(wife).start();
}
}
//账户
class Account{
int money; //余额
String name; //卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account; //账户
//取了多少钱
int drawingMoney;
//手里的钱
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
super(name); //调用父类Thread的name
this.account = account;
this.drawingMoney = drawingMoney;
}
// 取钱
public void run(){
//判断有没有钱
if(account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}
try { //休眠有助于放大问题
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额=余额-取出的钱
account.money=account.money-drawingMoney;
//去除的钱
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"余额为"+account.money);
/*由于这里继承了Thread,所以可以直接用this.getName()
等价于Thread.currentThread().getName()
*/
System.out.println(this.getName()+"手里钱为"+nowMoney);
}
}
3)线程不安全集合
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
List list = new ArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(
()->{
list.add(Thread.currentThread().getName());
}
).start();
}
Thread.sleep(3000); //睡眠三秒,让一万个线程有足够的时间加到集合里
System.out.println(list.size());
}
}
◆由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块.
同步方法: public synchronized void method(int args) {}
◆synchronized方法控制对 “对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行 ,
就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
缺陷:若将一个大的方法申明为synchronized将会影响效率。
方法里面需要修改的内容才需要锁,锁的太多,浪费资源。
同步块: synchronized (Obj){}
Obj称之为同步监视器
◆Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
◆同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是这个对象本身,或者是class
同步监视器的执行过程
1. 第一个线程访问,锁定同步监视器,执行其中代码
2.第二个线程访问 ,发现同步监视器被锁定,无法访问
3.第一个线程访问完毕,解锁同步监视器
4.第二个线程访问, 发现同步监视器没有锁,然后锁定并访问
使用synchronize来修改三大线程不安全案例里的买票,只需在buy方法里加个关键字synchronized,相当于修饰符
private synchronized void buy() throws InterruptedException {
//判断是否有票
if(ticketName<=0){
flag=false;
return;
}
//模拟延时
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"拿到"+ticketName--);
}
使用使用synchronize来修改三大线程不安全案例里的取钱,这个要用同步块
默认锁的是this,即synchronized(this),this表示这个对象,这个类,也就是Drawing银行这个类,所以没有作用,锁的东西取决于进程同时执行的对象(变化的),锁的对象是唯一的。
public void run(){
//锁的对象得是变化的量,就是增删改的量
synchronized (account){
//判断有没有钱
if(account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}
try { //休眠有助于放大问题
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额=余额-取出的钱
account.money=account.money-drawingMoney;
//去除的钱
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"余额为"+account.money);
/*由于这里继承了Thread,所以可以直接用this.getName()
等价于Thread.currentThread().getName()
*/
System.out.println(this.getName()+"手里钱为"+nowMoney);
}
}
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
List list = new ArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(
()->{
synchronized (list){ //锁住list集合
list.add(Thread.currentThread().getName());
}
}
).start();
}
Thread.sleep(3000);
System.out.println(list.size());
}
}
CopyOnWriteArrayList
使用了一种叫写时复制的方法,
CopyOnWriteArrayList
时,CopyOnWriteArrayList
的整个add操作都是在锁的保护下进行的。
add源码如下:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的。juc下的包非常多,举个CopyOnWriteArrayList例子
测试JUC安全类型的集合
import java.util.concurrent.CopyOnWriteArrayList;
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList list=new CopyOnWriteArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()-> {
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
多个线程各自占有一-些共享资源 ,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形. 某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题.
产生死锁的必要条件:
任何线程进入同步代码块、同步方法之前,必须获得同步监视器的锁定,那么何时会释放这个锁定呢?在程序中,是无法显式释放对同步监视器的锁的,而会在如下几个情况下释放锁。
1、当前线程的同步方法、代码块执行结束的时候释放
2、当前线程在同步方法、同步代码块中遇到break 、 return 终于该代码块或者方法的时候释放。
3、出现未处理的error或者exception导致异常结束的时候释放
4、程序执行了 同步对象 wait 方法 ,当前线程暂停,释放锁
如下情况不会释放锁
1、程序调用 Thread.sleep() Thread.yield() 这些方法暂停线程的执行,不会释放。
2、线程执行同步代码块时,其他线程调用 suspend 方法将该线程挂起,该线程不会释放锁 ,所以我们应该避免使用 suspend 和 resume 来控制线程。
死锁代码示例,两个线程是同步进行的,第一个线程先获得t1,然后停一毫秒,目的是为了让第二个线程有时间拿到t2对象,防止cpu把两个对象都只分配给其中一个线程。然后第一个线程继续执行里面的代码块,即要获得t2对象,此时由于获取t2的代码块写在synchronized (t1){}里面,所以t1对象并没有释放,因为里面的代码块没有执行结束。
同理第二个线程先获得t2对象,然后sleep要获取t1对象,由于t1对象仍然在第一个线程的手里没有释放,不可能拿到,t2对象又不可能被第一个线程拿到,所以两边相互等待,成了死锁。
public class DieLock {
public static Object t1 = new Object();
public static Object t2 = new Object();
public static void main(String[] args){
new Thread(){
@Override
public void run(){
synchronized (t1){
System.out.println("Thread1 get t1");
try {
Thread.sleep(100);
}catch (Exception e){
}
synchronized (t2){
System.out.println("Thread2 get t2");
}
}
}
}.start();
new Thread(){
@Override
public void run(){
synchronized (t2){
System.out.println("Thread2 get t2");
try {
Thread.sleep(100);
}catch (Exception e){
}
synchronized (t1){
System.out.println("Thread2 get t1");
}
}
}
}.start();
}
}
从JDK 5.0开始,Java提供了更强大的线程同步机制一通过 显式定义同步锁对象来实现同步。同步锁使用Lock对象充当java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是Reentrantlock,可以显式加锁、释放锁。作用与synchronized一致。
class A{
private final ReentrantLock lock = new ReentrantLock();
public void m(){
lock.lock();
try {
//这里放保证线程安全的代码
}finally {
lock.unlock();
//如果同步代码有异常,要将unlock()写入finally语句块
}
}
}
synchronized与Lock的对比
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNums = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//在这个if代码块加锁
try {
lock.lock(); //加锁,一般要用try-finally包围起来,因为涉及到手动解锁
//代码块开始
if(ticketNums>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+ticketNums--);
}else{
break;
}
//代码块结束
} finally {
lock.unlock(); //解锁
}
}
}
}
应用场景:生产者和消费者问题
线程通信分析
这是一个线程同步问题,生产者和消费者共享同-个资源,并且生产者和消费者之间相互依赖,互为条件.
Java提供了几个方法解决线程之间的通信问题
注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常legalMonitorStateException
并发协作模型“生产者/消费者模式”-->管程法
思路:
1.首先有一个生产者,消费者、生产者只顾生产,消费者只管消费、
2.利用了一个缓冲区,缓冲了一个10个大小的数组
3.有个方法叫放入产品,产品丢进来的时候,我们判断一下缓冲区有没有满,如果满了的话,生产者就要等待了,
如果没有满,就将产品放进去,放进去之后有产品了,赶紧通知消费者消费
4.消费者就判断下能不能消费呢,有没有东西,有东西的话,我就可以直接消费,消费完了,就赶紧通知生产者生产。
如果没有东西呢,消费者就等待。等待生产者去通知他,生产者通知了,他就可以解除等待了。
public class guancheng {
//生产者消费者模式,主要是用来借助一个缓冲区,管程法.
public static void main(String[] args) {
huanchong hc =new huanchong();
new shengchan(hc).start();
new xiaofei(hc).start();
}
}
class shengchan extends Thread{
huanchong hc;
public shengchan(huanchong hc) {
this.hc =hc;
}
public void run() {
for(int i=0; i<10; i++) {
System.out.println("生产了"+i+"个馒头");
hc.push(new mantou(i));
}
}
}
class xiaofei extends Thread{
huanchong hc;
public xiaofei(huanchong hc) {
this.hc =hc;
}
public void run() {
for(int i=0; i<100; i++) {
System.out.println("消费了"+hc.pop().id+"个馒头");
}
}
}
class huanchong{
mantou[] mt =new mantou[10];
int count=0;
//存取
public synchronized void push(mantou m) {
if(count==mt.length) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
mt[count++]=m;
//有了空间之后。通知消费者消费
this.notifyAll();
}
//消费
public synchronized mantou pop() {
if(count==0) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
count--;
mantou m=mt[count];
//没有空间,通知生产者生产
this.notifyAll();
return m;
}
}
class mantou{
int id;
public mantou(int id) {
this.id=id;
}
}
来判断一个标志位flag,如果为true,就让他等待、如果为false,就让他去通知另外一个人、把两人衔接起来,就像咱们的信号灯红灯停,绿灯行,通过这样一个判断方式,只要来判断什么时候让他等待,什么时候将他唤醒即可。
package com.thread.gaoji;
//测试生产者消费者问题2:信号灯法,通过标志位解决
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者-->演员
class Player extends Thread {
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("快乐大本营播放中");
} else {
this.tv.play("抖音:记录美好生活");
}
}
}
}
//消费者-->观众
class Watcher extends Thread {
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//产品-->节目
class TV {
//演员表演,观众等待 T
//观众观看,演员等待 F
String voice; // 表演的节目
boolean flag = true;
//表演
public synchronized void play(String voice) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:" + voice);
//通知观众观看
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
//观看
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:" + voice);
//通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:◆提高响应速度(减少了创建新线程的时间)
◆降低资源消耗(重复利用线程池中线程,不需要每次都创建
◆便于线程管理
◆corePoolSize: 核心池的大小
◆maximumPoolSize:最大线程数
◆keepAliveTime: 线程没有任务时最多保持多长时间后会终止
1. 线程池的概念:
线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
2. 线程池的工作机制
2.1 在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。
2.1 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
3. 使用线程池的原因:
多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过渡切换线程的危险,从而可能导致系统资源的崩溃。这时,线程池就是最好的选择了。
1. 线程池的返回值ExecutorService简介:ExecutorService是Java提供的用于管理线程池的类。该类的两个作用:控制线程数量和重用线程。
2. 具体的4种常用的线程池实现如下:(返回值都是ExecutorService)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewCachedThreadPoolTest {
public static void main(String[] args) {
// 创建一个可缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
try {
// sleep可明显看到使用的是线程池里面以前的线程,没有创建新的线程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
// 打印正在执行的缓存线程信息
System.out.println(Thread.currentThread().getName()
+ "正在被执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewFixedThreadPoolTest {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool参数为:线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
System.out.println(Runtime.getRuntime().availableProcessors());
//2.关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class NewScheduledThreadPoolTest {
public static void main(String[] args) {
//创建一个定长线程池,支持定时及周期性任务执行——延迟执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//延迟1秒执行
/*scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("延迟1秒执行");
}
}, 1, TimeUnit.SECONDS);*/
//延迟1秒后每3秒执行一次
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("延迟1秒后每3秒执行一次");
}
}, 1, 3, TimeUnit.SECONDS);
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewSingleThreadExecutorTest {
public static void main(String[] args) {
//创建一个单线程化的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
//结果依次输出,相当于顺序执行各个任务
System.out.println(Thread.currentThread().getName()+"正在被执行,打印的值是:"+index);
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
1. 缓冲队列BlockingQueue简介:
BlockingQueue是双缓冲队列。BlockingQueue内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。
2. 常用的几种BlockingQueue:
ArrayBlockingQueue(int i):规定大小的BlockingQueue,其构造必须指定大小。其所含的对象是FIFO顺序排序的。
LinkedBlockingQueue()或者(int i):大小不固定的BlockingQueue,若其构造时指定大小,生成的BlockingQueue有大小限制,不指定大小,其大小有Integer.MAX_VALUE来决定。其所含的对象是FIFO顺序排序的。
PriorityBlockingQueue()或者(int i):类似于LinkedBlockingQueue,但是其所含对象的排序不是FIFO,而是依据对象的自然顺序或者构造函数的Comparator决定。
SynchronizedQueue():特殊的BlockingQueue,对其的操作必须是放和取交替完成。
3. 自定义线程池(ThreadPoolExecutor和BlockingQueue连用):
自定义线程池,可以用ThreadPoolExecutor类创建,它有多个构造方法来创建线程池。
常见的构造函数:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ZiDingYiThreadPoolExecutor {
static class TempThread implements Runnable {
@Override
public void run() {
// 打印正在执行的缓存线程信息
System.out.println(Thread.currentThread().getName() + "正在被执行");
try {
// sleep一秒保证3个任务在分别在3个线程上执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建数组型缓冲等待队列
BlockingQueue bq = new ArrayBlockingQueue(10);
// ThreadPoolExecutor:创建自定义线程池,池中保存的线程数为3,允许最大的线程数为6
ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 6, 50, TimeUnit.MILLISECONDS, bq);
// 创建3个任务
Runnable t1 = new TempThread();
Runnable t2 = new TempThread();
Runnable t3 = new TempThread();
Runnable t4 = new TempThread();
Runnable t5 = new TempThread();
Runnable t6 = new TempThread();
// 3个任务分别在3个线程上执行
tpe.execute(t1);
tpe.execute(t2);
tpe.execute(t3);
tpe.execute(t4);
tpe.execute(t5);
tpe.execute(t6);
// 关闭自定义线程池
tpe.shutdown();
}
}