一、线程相关概念:
1、程序(program):
是为完成特定任务、用某种语言编写的一组指令的集合,即我们写的代码。
2、进程:
(1)进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
(2)进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程.
3、什么是线程:
(1)线程由进程创建的,是进程的一个实体
(2)一个进程可以拥有多个线程
4、单线程:
同一个时刻,只允许执行一个线程。
5、多线程:
同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件。
6、并发:
同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单地说,单核cpu实现的多任务就是并发
7、并行:(有同伴一起走)
同一个时刻,多个任务同时执行,多核cpu可以实现并行
·查看电脑CPU的方式:
方式一:右击屏幕底下的长条形任务栏,打开“任务管理器”,“打开资源管理器”
方式二:右击“我的电脑”,选择“管理”,“设备管理器”,“处理器”
方式三:用java代码查看
package event_;
public class CpuNum {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
//获取当前电脑的cpu数量/核心数
int cpuNums = runtime.availableProcessors();
System.out.println("当前有cpu个数:" + cpuNums);
}
}
//当前有cpu个数:8
我的是4核8线程
二、线程基本使用
1、创建线程的两种方式
(1)继承Thread类,重写run方法
package threaduse;
public class Thread01 {
public static void main(String[] args) throws Exception{
//创建Cat对象,可以当做线程使用
Cat cat=new Cat();
cat.start();//启动线程
//当main线程启动一个子线程Thread-0,主线程不会阻塞,会继续执行
//这时,主线程和子线程是交替执行
System.out.println("主线程继续执行"+Thread.currentThread().getName());
for(int i=0;i<10;i++){
System.out.println("主线程 i="+i);
//让主线程休眠
Thread.sleep(1000);
}
}
}
//1、当一个类继承了Thread,该类就可以当做线程使用
//2、要重写run方法,写上自己的业务逻辑
//3、run方法是Thread类实现了Runnable接口用的
/* @Override
public void run() {
Runnable task = holder.task;
if (task != null) {
task.run();
}
}
*/
class Cat extends Thread{
int times=0;
public void run() {
//如果不加循环,代码执行一次就退出了
while(true){
//该线程每隔1秒,在控制台输出"喵喵,我是小猫咪"
System.out.println("喵喵,我是小猫咪"+(++times)+" 线程名:"+Thread.currentThread().getName());
//让线程休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(times==10){
break;
}
}
}
}
//主线程继续执行main
//主线程 i=0
//喵喵,我是小猫咪1 线程名:Thread-0
//喵喵,我是小猫咪2 线程名:Thread-0
//主线程 i=1
//喵喵,我是小猫咪3 线程名:Thread-0
//主线程 i=2
//喵喵,我是小猫咪4 线程名:Thread-0
//主线程 i=3
//喵喵,我是小猫咪5 线程名:Thread-0
//主线程 i=4
//喵喵,我是小猫咪6 线程名:Thread-0
//主线程 i=5
//主线程 i=6
//喵喵,我是小猫咪7 线程名:Thread-0
//主线程 i=7
//喵喵,我是小猫咪8 线程名:Thread-0
//主线程 i=8
//喵喵,我是小猫咪9 线程名:Thread-0
//主线程 i=9
//喵喵,我是小猫咪10 线程名:Thread-0
深入解析cat.start();方法
//源码:
/*1、
void start(ThreadContainer container) {
synchronized (this) {
start0();
}
}
2、
//start0()是酵方法,是JVM调用,底层是c/c++实现
//真正实现多线程的效果,是start0(),而不是run()
private native void start0();
*/
cat.start();//启动线程--->最终会执行cat的run方法
cat.run();//如果直接调用run()方法,这里的run()方法就是一个普通的方法,没有真正地启动一个线程
//等执行完这个run()方法后,才继续往下走,此时已经不再是多线程了
//在main里调用run()方法,输出的就是main的线程名,不是要启动的线程名
//输出:喵喵,我是小猫咪1 线程名:main
补:主线程决定进程开启,最后结束的线程决定进程结束
(2)实现Runinable接口,重写run方法
package threaduse;
public class Thread02 {
public static void main(String[] args) {
Dog dog=new Dog();
//dog.start();报错,这里不能调用start
//创建了Thread对象,把dog对象(实现Runnable),放入Thread
Thread thread = new Thread(dog);
thread.start();
Tiger tiger = new Tiger();
ThreadProxy threadProxy = new ThreadProxy(tiger);
threadProxy.start();
}
}
class Animal{}
class Tiger extends Animal implements Runnable{
@Override
public void run() {
System.out.println("老虎嗷嗷叫...");
}
}
//线程代理类,模拟了一个极简的Thread类
class ThreadProxy implements Runnable{//
private Runnable target=null;//类型是Runnable
@Override
public void run() {
if (target != null) {
target.run();//动态绑定,运行类型是传进来的参数的类型
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start(){
start0();//这个方法才是真正实现多线程的方法
}
public void start0(){
run();
}
}
class Dog implements Runnable{
int count=0;
@Override
public void run() {
while(true){
System.out.println("小狗汪汪叫...hi"+(++count)+Thread.currentThread().getName());
try {
Thread.sleep(1000);//休眠一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
线程这样设计的意义,最根本的意义就在于解耦,权责分明:把创建线程交给Thread类,创建线程任务交给实现了Runnable接口的任意类
这样做的好处:
(1)解除类的单继承限制,更好地实现多线程;
(2)解耦之前,线程任务改动需要修改run()方法,解耦后,只需要修改外部类,实现非侵入式的修改,提高代码的可扩展性,这是接口的主要好处
(3)线程任务执行完后,不需要再次创建、销毁线程,只需要实现Runnable接口提交新的的线程任务即可,提高性能节省开销,后面的线程池设计主要体现这一点
2、应用线程
package threaduse;
import javax.swing.plaf.TableHeaderUI;
@SuppressWarnings({"all"})
public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread1=new Thread(t1);
Thread thread2= new Thread(t2);
thread1.start();
thread2.start();
}
}
class T1 implements Runnable{
int count=0;
@Override
public void run() {
while (true) {
System.out.println("hello, world! "+(++count)+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
class T2 implements Runnable{
int count=0;
@Override
public void run() {
while (true) {
System.out.println("hi "+(++count)+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
break;
}
}
}
}
//hello, world! 1Thread-0
//hi 1Thread-1
//hello, world! 2Thread-0
//hi 2Thread-1
//hi 3Thread-1
//hello, world! 3Thread-0
//hello, world! 4Thread-0
//hi 4Thread-1
//hello, world! 5Thread-0
//hi 5Thread-1
//hello, world! 6Thread-0
//hello, world! 7Thread-0
//hello, world! 8Thread-0
//hello, world! 9Thread-0
//hello, world! 10Thread-0
3、继承Thread和实现Runnable的区别:
(1)从java的设计来看,通过继承Thread或实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口,都是通过调用start()----->最终实现调用start0();
(2)实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用
T3 t3 = new T3("Hello");//一个对象
Thread thread01=new Thread(t3);
Thread thread02=new Thread(t3);
thread01.start();
thread02.start();
System.out.println("主线程完毕");
4、
//使用Thread
package ticket;
public class SellTicket {
public static void main(String[] args) {
SellTicket01 sellTicket1 = new SellTicket01();
SellTicket01 sellTicket2 = new SellTicket01();
SellTicket01 sellTicket3 = new SellTicket01();
//三个售票窗口
sellTicket1.start();
sellTicket2.start();
sellTicket3.start();
}
}
class SellTicket01 extends Thread{
private static int ticketNum=10;//让多个线程共享ticketNum
public void run(){
while(true){
if (ticketNum <= 0) {
System.out.println("售票结束");
break;
}
try {
Thread.sleep(50);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
+" 剩余票数="+(--ticketNum));
}
}
}
//窗口 Thread-1 售出一张票 剩余票数=9
//窗口 Thread-0 售出一张票 剩余票数=7
//窗口 Thread-2 售出一张票 剩余票数=8
//窗口 Thread-2 售出一张票 剩余票数=6
//窗口 Thread-0 售出一张票 剩余票数=4
//窗口 Thread-1 售出一张票 剩余票数=5
//窗口 Thread-2 售出一张票 剩余票数=3
//窗口 Thread-1 售出一张票 剩余票数=2
//窗口 Thread-0 售出一张票 剩余票数=1
//窗口 Thread-1 售出一张票 剩余票数=0
//窗口 Thread-2 售出一张票 剩余票数=0
//售票结束
//窗口 Thread-0 售出一张票 剩余票数=-1
//售票结束
//售票结束
//出现了超卖现象,因为最后的时候,三个线程同时进入到while循环,相差时间太短
//使用Runnable
package ticket;
public class SellTicket {
public static void main(String[] args) {
SellTicket02 sellTicket = new SellTicket02();
//三个售票窗口
new Thread(sellTicket).start();
new Thread(sellTicket).start();
new Thread(sellTicket).start();
}
}
//使用Thread
class SellTicket02 implements Runnable{
private static int ticketNum=10;//让多个线程共享ticketNum
public void run(){
while(true){
if (ticketNum <= 0) {
System.out.println("售票结束");
break;
}
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
+" 剩余票数="+(--ticketNum));
}
}
}
//窗口 Thread-1 售出一张票 剩余票数=9
//窗口 Thread-0 售出一张票 剩余票数=8
//窗口 Thread-2 售出一张票 剩余票数=8
//窗口 Thread-2 售出一张票 剩余票数=7
//窗口 Thread-1 售出一张票 剩余票数=7
//窗口 Thread-0 售出一张票 剩余票数=6
//窗口 Thread-0 售出一张票 剩余票数=4
//窗口 Thread-1 售出一张票 剩余票数=5
//窗口 Thread-2 售出一张票 剩余票数=3
//窗口 Thread-1 售出一张票 剩余票数=2
//窗口 Thread-2 售出一张票 剩余票数=2
//窗口 Thread-0 售出一张票 剩余票数=1
//窗口 Thread-2 售出一张票 剩余票数=0
//售票结束
//窗口 Thread-1 售出一张票 剩余票数=-1
//售票结束
//窗口 Thread-0 售出一张票 剩余票数=0
//售票结束
//出现了超卖现象,因为最后的时候,三个线程同时进入到while循环,相差时间太短
出现了问题:票数超卖,多个线程同时进入循环,都检测到了最后的一个资源,所以都卖了出去,出现了超卖现象。
5、线程终止:
(1)基本说明:
1)当线程完成任务后,会自动退出
2)还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
(2)应用案例:
6、线程常用方法:
补:中断可以类比闹钟来理解
//解释join:线程插队
package threaduse;
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T3 t3 = new T3();
t3.start();
for(int i=1;i<=20;i++){
Thread.sleep(1000);
System.out.println("主线程(小弟)吃了 "+i+" 包子");
if(i==5){
System.out.println("主线程(小弟)让了子线程(老大)先吃");
t3.join();//这里相当于让t2线程先执行完毕
System.out.println("子线程(老大)吃完了,主线程(小弟)接着吃...");
}
}
}
}
class T3 extends Thread{
public void run(){
for(int i=1;i<=20;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程(老大)吃了 " +i+" 包子");
}
}
}
//主线程(小弟)吃了 1 包子
//子线程(老大)吃了 1 包子
//主线程(小弟)吃了 2 包子
//子线程(老大)吃了 2 包子
//子线程(老大)吃了 3 包子
//主线程(小弟)吃了 3 包子
//子线程(老大)吃了 4 包子
//主线程(小弟)吃了 4 包子
//子线程(老大)吃了 5 包子
//主线程(小弟)吃了 5 包子
//主线程(小弟)让了子线程(老大)先吃
//子线程(老大)吃了 6 包子
//子线程(老大)吃了 7 包子
//子线程(老大)吃了 8 包子
//子线程(老大)吃了 9 包子
//子线程(老大)吃了 10 包子
//子线程(老大)吃了 11 包子
//子线程(老大)吃了 12 包子
//子线程(老大)吃了 13 包子
//子线程(老大)吃了 14 包子
//子线程(老大)吃了 15 包子
//子线程(老大)吃了 16 包子
//子线程(老大)吃了 17 包子
//子线程(老大)吃了 18 包子
//子线程(老大)吃了 19 包子
//子线程(老大)吃了 20 包子
//子线程(老大)吃完了,主线程(小弟)接着吃...
//主线程(小弟)吃了 6 包子
//主线程(小弟)吃了 7 包子
//主线程(小弟)吃了 8 包子
//主线程(小弟)吃了 9 包子
//主线程(小弟)吃了 10 包子
//主线程(小弟)吃了 11 包子
//主线程(小弟)吃了 12 包子
//主线程(小弟)吃了 13 包子
//主线程(小弟)吃了 14 包子
//主线程(小弟)吃了 15 包子
//主线程(小弟)吃了 16 包子
//主线程(小弟)吃了 17 包子
//主线程(小弟)吃了 18 包子
//主线程(小弟)吃了 19 包子
//主线程(小弟)吃了 20 包子
//解释yeild:礼让,不一定成功
package threaduse;
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T3 t3 = new T3();
t3.start();
for(int i=1;i<=20;i++){
Thread.sleep(1000);
System.out.println("主线程(小弟)吃了 "+i+" 包子");
if(i==5){
System.out.println("主线程(小弟)让了子线程(老大)先吃");
Thread.yield();//礼让,不一定成功...
System.out.println("子线程(老大)吃完了,主线程(小弟)接着吃...");
}
}
}
}
class T3 extends Thread{
public void run(){
for(int i=1;i<=20;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程(老大)吃了 " +i+" 包子");
}
}
}
//主线程(小弟)吃了 1 包子
//子线程(老大)吃了 1 包子
//子线程(老大)吃了 2 包子
//主线程(小弟)吃了 2 包子
//子线程(老大)吃了 3 包子
//主线程(小弟)吃了 3 包子
//子线程(老大)吃了 4 包子
//主线程(小弟)吃了 4 包子
//主线程(小弟)吃了 5 包子
//主线程(小弟)让了子线程(老大)先吃
//子线程(老大)吃了 5 包子
//子线程(老大)吃完了,主线程(小弟)接着吃...
//主线程(小弟)吃了 6 包子
//子线程(老大)吃了 6 包子
//子线程(老大)吃了 7 包子
//主线程(小弟)吃了 7 包子
//主线程(小弟)吃了 8 包子
//子线程(老大)吃了 8 包子
//子线程(老大)吃了 9 包子
//主线程(小弟)吃了 9 包子
//主线程(小弟)吃了 10 包子
//子线程(老大)吃了 10 包子
//主线程(小弟)吃了 11 包子
//子线程(老大)吃了 11 包子
//主线程(小弟)吃了 12 包子
//子线程(老大)吃了 12 包子
//主线程(小弟)吃了 13 包子
//子线程(老大)吃了 13 包子
//主线程(小弟)吃了 14 包子
//子线程(老大)吃了 14 包子
//主线程(小弟)吃了 15 包子
//子线程(老大)吃了 15 包子
//主线程(小弟)吃了 16 包子
//子线程(老大)吃了 16 包子
//主线程(小弟)吃了 17 包子
//子线程(老大)吃了 17 包子
//主线程(小弟)吃了 18 包子
//子线程(老大)吃了 18 包子
//主线程(小弟)吃了 19 包子
//子线程(老大)吃了 19 包子
//主线程(小弟)吃了 20 包子
//子线程(老大)吃了 20 包子
7、练习题:
//我的代码
package threaduse;
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {
T4 t4 = new T4();
Thread thread = new Thread(t4);
for(int i=1;i<=10;i++){
System.out.println("hi "+i);
if (i == 5) {
thread.start();
thread.join();
}
Thread.sleep(1000);
}
System.out.println("主线程结束...");
}
}
class T4 implements Runnable{
public void run(){
for(int i=1;i<=10;i++){
System.out.println("hello"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("子线程结束...");
}
}
//hi 1
//hi 2
//hi 3
//hi 4
//hi 5
//hello1
//hello2
//hello3
//hello4
//hello5
//hello6
//hello7
//hello8
//hello9
//hello10
//子线程结束...
//hi 6
//hi 7
//hi 8
//hi 9
//hi 10
//主线程结束...
8、用户线程和守护线程:
(1)用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
(2)守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
(3)常见的守护线程:垃圾回收机制
package threaduse;
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我们希望当main线程结束后,子线程自动结束
//只需将子线程设置守线程即可
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for(int i=1;i<=10;i++){
System.out.println("宝强在辛苦地工作...");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread{
public void run(){
for(;;){//无限循环
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("马蓉和宋喆居心叵测聊天,哈哈哈~~~");
}
}
}
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
//宝强在辛苦地工作...
//马蓉和宋喆居心叵测聊天,哈哈哈~~~
9、线程的生命周期
·如上为官方的文档,所以线程一共有6种状态
package threaduse;
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
T5 t5 = new T5();
System.out.println(t5.getName()+" 状态"+ t5.getState());
t5.start();
while (Thread.State.TERMINATED != t5.getState()) {
System.out.println(t5.getName()+" 状态"+ t5.getState());
Thread.sleep(500);
}
System.out.println(t5.getName()+" 状态"+ t5.getState());
}
}
class T5 extends Thread{
public void run(){
while (true) {
for(int i=0;i<10;i++){
System.out.println("hi "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
//Thread-0 状态NEW
//Thread-0 状态RUNNABLE
//hi 0
//Thread-0 状态TIMED_WAITING
//hi 1
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 2
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 3
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 4
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 5
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 6
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 7
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 8
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//hi 9
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TIMED_WAITING
//Thread-0 状态TERMINATED
10、线程的同步:
(1)在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
(2)或者理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
(3)
(4)解决售票超卖问题:
//使用Runnable
package ticket;
public class SellTicket {
public static void main(String[] args) throws InterruptedException {
SellTicket02 sellTicket = new SellTicket02();
//三个售票窗口
new Thread(sellTicket).start();
new Thread(sellTicket).start();
new Thread(sellTicket).start();
}
}
//使用Thread
class SellTicket02 implements Runnable{
private static int ticketNum=100;
private boolean loop=true;
public boolean isLoop() {
return loop;
}
public void setLoop(boolean loop) {
this.loop = loop;
}
public synchronized void sell(){//同步方法,在同一时刻,只能有一个线程来执行sell方法
if (ticketNum <= 0) {
System.out.println("售票结束");
loop=false;
return;
}
try {
Thread.sleep(50);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
+" 剩余票数="+(--ticketNum));
}
public void run(){
while(loop){
sell();
}
}
}
//窗口 Thread-0 售出一张票 剩余票数=99
//窗口 Thread-0 售出一张票 剩余票数=98
//窗口 Thread-0 售出一张票 剩余票数=97
//窗口 Thread-0 售出一张票 剩余票数=96
//窗口 Thread-0 售出一张票 剩余票数=95
//窗口 Thread-0 售出一张票 剩余票数=94
//窗口 Thread-0 售出一张票 剩余票数=93
//窗口 Thread-0 售出一张票 剩余票数=92
//窗口 Thread-0 售出一张票 剩余票数=91
//窗口 Thread-0 售出一张票 剩余票数=90
//窗口 Thread-0 售出一张票 剩余票数=89
//窗口 Thread-2 售出一张票 剩余票数=88
//窗口 Thread-1 售出一张票 剩余票数=87
//窗口 Thread-1 售出一张票 剩余票数=86
//窗口 Thread-1 售出一张票 剩余票数=85
//窗口 Thread-2 售出一张票 剩余票数=84
//窗口 Thread-2 售出一张票 剩余票数=83
//窗口 Thread-2 售出一张票 剩余票数=82
//窗口 Thread-2 售出一张票 剩余票数=81
//窗口 Thread-2 售出一张票 剩余票数=80
//窗口 Thread-2 售出一张票 剩余票数=79
//窗口 Thread-2 售出一张票 剩余票数=78
//窗口 Thread-2 售出一张票 剩余票数=77
//窗口 Thread-2 售出一张票 剩余票数=76
//窗口 Thread-2 售出一张票 剩余票数=75
//窗口 Thread-0 售出一张票 剩余票数=74
//窗口 Thread-0 售出一张票 剩余票数=73
//窗口 Thread-0 售出一张票 剩余票数=72
//窗口 Thread-0 售出一张票 剩余票数=71
//窗口 Thread-0 售出一张票 剩余票数=70
//窗口 Thread-0 售出一张票 剩余票数=69
//窗口 Thread-0 售出一张票 剩余票数=68
//窗口 Thread-0 售出一张票 剩余票数=67
//窗口 Thread-0 售出一张票 剩余票数=66
//窗口 Thread-0 售出一张票 剩余票数=65
//窗口 Thread-0 售出一张票 剩余票数=64
//窗口 Thread-0 售出一张票 剩余票数=63
//窗口 Thread-0 售出一张票 剩余票数=62
//窗口 Thread-0 售出一张票 剩余票数=61
//窗口 Thread-0 售出一张票 剩余票数=60
//窗口 Thread-0 售出一张票 剩余票数=59
//窗口 Thread-0 售出一张票 剩余票数=58
//窗口 Thread-2 售出一张票 剩余票数=57
//窗口 Thread-2 售出一张票 剩余票数=56
//窗口 Thread-2 售出一张票 剩余票数=55
//窗口 Thread-2 售出一张票 剩余票数=54
//窗口 Thread-1 售出一张票 剩余票数=53
//窗口 Thread-2 售出一张票 剩余票数=52
//窗口 Thread-2 售出一张票 剩余票数=51
//窗口 Thread-2 售出一张票 剩余票数=50
//窗口 Thread-2 售出一张票 剩余票数=49
//窗口 Thread-2 售出一张票 剩余票数=48
//窗口 Thread-2 售出一张票 剩余票数=47
//窗口 Thread-2 售出一张票 剩余票数=46
//窗口 Thread-0 售出一张票 剩余票数=45
//窗口 Thread-0 售出一张票 剩余票数=44
//窗口 Thread-0 售出一张票 剩余票数=43
//窗口 Thread-2 售出一张票 剩余票数=42
//窗口 Thread-2 售出一张票 剩余票数=41
//窗口 Thread-2 售出一张票 剩余票数=40
//窗口 Thread-2 售出一张票 剩余票数=39
//窗口 Thread-1 售出一张票 剩余票数=38
//窗口 Thread-1 售出一张票 剩余票数=37
//窗口 Thread-2 售出一张票 剩余票数=36
//窗口 Thread-2 售出一张票 剩余票数=35
//窗口 Thread-2 售出一张票 剩余票数=34
//窗口 Thread-2 售出一张票 剩余票数=33
//窗口 Thread-2 售出一张票 剩余票数=32
//窗口 Thread-2 售出一张票 剩余票数=31
//窗口 Thread-2 售出一张票 剩余票数=30
//窗口 Thread-2 售出一张票 剩余票数=29
//窗口 Thread-2 售出一张票 剩余票数=28
//窗口 Thread-0 售出一张票 剩余票数=27
//窗口 Thread-0 售出一张票 剩余票数=26
//窗口 Thread-0 售出一张票 剩余票数=25
//窗口 Thread-0 售出一张票 剩余票数=24
//窗口 Thread-0 售出一张票 剩余票数=23
//窗口 Thread-0 售出一张票 剩余票数=22
//窗口 Thread-2 售出一张票 剩余票数=21
//窗口 Thread-2 售出一张票 剩余票数=20
//窗口 Thread-2 售出一张票 剩余票数=19
//窗口 Thread-2 售出一张票 剩余票数=18
//窗口 Thread-1 售出一张票 剩余票数=17
//窗口 Thread-2 售出一张票 剩余票数=16
//窗口 Thread-2 售出一张票 剩余票数=15
//窗口 Thread-2 售出一张票 剩余票数=14
//窗口 Thread-0 售出一张票 剩余票数=13
//窗口 Thread-2 售出一张票 剩余票数=12
//窗口 Thread-2 售出一张票 剩余票数=11
//窗口 Thread-1 售出一张票 剩余票数=10
//窗口 Thread-1 售出一张票 剩余票数=9
//窗口 Thread-1 售出一张票 剩余票数=8
//窗口 Thread-1 售出一张票 剩余票数=7
//窗口 Thread-1 售出一张票 剩余票数=6
//窗口 Thread-2 售出一张票 剩余票数=5
//窗口 Thread-2 售出一张票 剩余票数=4
//窗口 Thread-2 售出一张票 剩余票数=3
//窗口 Thread-2 售出一张票 剩余票数=2
//窗口 Thread-0 售出一张票 剩余票数=1
//窗口 Thread-0 售出一张票 剩余票数=0
//售票结束
//售票结束
//售票结束
11、互斥锁:
(1)基本介绍:
1)Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2)每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3)关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
4)同步的局限性:导致程序的执行效率要降低
5)同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6)同步方法(静态的)的锁为当前类本身。
class SellTicket02 implements Runnable {
//...
//1、public synchronized void sell(){}就是一个同步方法
public synchronized void sell01() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到
if (ticketNum <= 0) {
System.out.println("售票结束");
loop = false;
return;
}
try {
Thread.sleep(50);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
//2、这里锁在this对象
public void sell02() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到
synchronized (this) {
if (ticketNum <= 0) {
System.out.println("售票结束");
loop = false;
return;
}
}
try {
Thread.sleep(50);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
//3、也可以在代码块上写synchronized,同步代码块
Object object=new Object();
public void sell03() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到
synchronized (object) {
if (ticketNum <= 0) {
System.out.println("售票结束");
loop = false;
return;
}
}
try {
Thread.sleep(50);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
//静态的同步方法的锁为当前类本身
//1、
public synchronized static void sell04() {}//锁是加在SellTicket02.class
//2、
public static void sell05(){
synchronized(SellTicket02.class){
System.out.println("sell05");
}
}
//...
}
(2) 注意事项和细节
1)同步方法如果没有使用static修饰:默认锁对象为this
2)如果方法使用static修饰,默认锁对象:当前类.class
3)实现的落地步骤:
需要先分析上锁的代码
选择同步代码块或同步方法
要求多个线程的锁对象为同一个即可!
12、线程的死锁
(1)基本介绍:
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生
package threaduse;
public class DeadLock {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B线程");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread{
static Object o1=new Object();//保证多线程,共享一个对象,这里使用static
static Object o2=new Object();
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
public void run(){
//下面业务逻辑分析:
//1、如果flag为T,线程A就会先得到/持有o1对象锁,然后尝试去获取o2对象锁
//2、如果线程A得不到o2对象锁,就会Blocked
//3、如果flag为T,线程B就会先得到/持有o1对象锁,然后尝试去获取o2对象锁
//4、如果线程B得不到o2对象锁,就会Blocked
if (flag) {
synchronized (o1){//对象互斥锁,下面就是同步代码
System.out.println(Thread.currentThread().getName()+"进入1");
synchronized (o2){//这里获得li对象的监视权
System.out.println(Thread.currentThread().getName()+"进入2");
}
}
}else{
synchronized (o2){//对象互斥锁,下面就是同步代码
System.out.println(Thread.currentThread().getName()+"进入3");
synchronized (o1){//这里获得li对象的监视权
System.out.println(Thread.currentThread().getName()+"进入4");
}
}
}
//我的理解:
//有线程走if,拿到了o1的锁,等待着o2的锁,没拿到就没法释放资源,但同时有线程走else,拿到了o2的锁,等待着o1的锁,所以它们永远不可能等到对方
}
}
//输出:(卡住了)
//A线程进入1
//B线程进入3
13、释放锁:
下面操作会释放锁:
(1)当前线程的同步方法、同步代码块执行结束
上厕所,完事出来
(2)当前线程的同步代码块、同步方法中遇到break, return
没有正常地完事,经理叫他修改bug,不得已出来
(3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
没有正常地完事,发现忘带纸,不得已出来
(4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
没有正常完事,觉得需要酝酿下,所以出来等会再进去
下面操作不会释放锁:
(1)线程执行同步代码块或同步方法时,程序调用Thread.sleep(), Thread.yield()方法暂停当前线程的执行,不会释放锁
上厕所,太困了,在坑位上眯了一会
(2)线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用