没学之前对于多线程的认识?
提到线程,那么第一个想到的就是进程。一个应用程序的运行基本单位就是一个进程,而进程是由线程组成的,一个进程最少有一个线程,进程中有一个主要的线程,叫做主线程,是java虚拟机创建,里面有main()方法。通过多线程,可以实现内存的充分使用,解决了时间,提高了效率。
多线程
重点:
- 进入多线程思想编程
- 结合Swing程序开发做好相应工作
什么是并发机制,并发机制有什么作用?
并行指在同一时间点,有多条指令在多个处理器上同时执行。
我们为什么需要并发呢?通常是为了提高程序的运行速度或者改善程序的设计。
什么是多线程机制?
在日常的生活中,我们做许多的事都是同时进行的,Java模拟这种状态,引入了多线程机制。
通过并发机制程序执行多个线程,每一个线程都完成一个功能,并于其他线程并发执行,这种机制叫做多线程。
多线程可以运用在哪些方面?
可以运用在创建窗口程序(Swing程序开发)、网络程序等。
线程是如何工作的?
当JVM启动时,通常有一个非守护的线程(它通常条用某个指定类的main方法),JVM继续执行线程,直到遇到以下任何一种情况时停止:
Runtime类的exit方法已经被调用,且安全管理器以允许执行退出操作(比如调用Thread.interrupt方法)
不是守护线程的所有线程都已死亡,要么从对run()方法的调用返回,要吗抛出一个在run()方法之前传播的异常
多线程在操作系统中是如何进行工作的呢?
Java多线程在每个操作系统中的运行方式都存在差异。因为Windows操作系统是多任务操作系统,以进程为单位。一个进程包括自身地址的程序,每个独立执行的程序都称为进程,也就是正在执行的程序。系统可以分配给每个进程有限的使用CPU的时间(被称为CPU时间片),这段时间CPU执行某段进程,下一个时间就会跳到另一个进程。因为CPU的执行速度非常快,所以看上去进程是同时执行的。
一个线程则是进程中的执行流程,一个进程中可以同时包含多个线程,每个线程也可以得到一小段程序执行时间,这样一个进程就可以拥有多个并发执行的线程。如果需要一个进程同时完成多段代码的操作,就需要产生多线程。
如何标识一个线程?
每个线程都有名字,多个线程可能具有相同的名字,Thread有的构造器如果没有指定名字,会自动生成一个名字
实现线程的两种方式
Java提供了哪两种实现线程的方式?
- 继承java.lang.Thread类
实现java.lang.Runnable接口
继承Thread类
通过实例化java.lang.Thread类,可以得到一个线程。
常见的构造方法:
构造方法 | 功能描述 |
---|---|
public Thread() | 创建一个新的线程对象 |
public Thread(String threadName) | 创建一个名称为threadName的线程对象 |
继承Thread类创建一个新的线程的语法规则:
public class ThreadTest extends Thread {}
之前的学习是程序是如何运行的?
当程序执行的时候,Java虚拟机会产生一个主线程,主方法就是在这个线程中运行的。如果不启动其他的线程,那么程序就是单线程程序。
Java虚拟机负责产生主线程,程序员负责启动自己的线程。
开启一个新线程代码是这样的:
public static void main(String[] args) {
new ThreadTest().start();
}
实例化线程对象之后如何使用?
完成线程真正功能的代码放在类的run()方法中,当一个类继承了Thread类后,就可以在该类中覆盖run()方法,将实现该线程的功能的代码写入run()方法中,然后同时调用Thread类中的start()方法执行线程,也就是调用run方法。如果只是调用run()方法,那么线程不算开启,只是简单地调用了run()方法。
如果使用的是run()方法,那么还是执行的主线程,没有开启指定线程。
run()方法的语法格式:
public void run() {}
注意:如果start()方法调用了一个已经启动的线程,那么系统会抛出IllegalThreadStateException
代码实现
package cn.wells;
public class ThreadTest extends Thread{
private int count = 10;
public void run() { //重写run()方法
while(true) {
System.out.print(count + " ");
if(--count == 0) {
return;
}
}
}
public static void main(String[] args) {
new ThreadTest().start();
//使用start()方法调用被覆盖的run()方法
}
}
注意:Thread对象是一个实例,而不是一个真正的线程。
实现Runnable接口
为什么有了Thread类,还需要有Runnable接口?
在Java中,一个类只能继承一个父类,一个拓展JFrame类的GUI程序不可以再继承Thread类。但是一个类可以继承多个接口,可以轻松解决这个问题,所以Java又提供了另一种实现线程的方法:Runnable接口
实现Runnable接口的语法格式:
public class Thread extends Object implements Runnable {}
Thread类为什么可以创建线程?
Thread类实际上继承了Runnable接口,其中的run()方法正是对Runnable接口中的run()方法的具体实现。
实现Runnable接口的程序会创建一个Thread对象,并将Runnable对象与Thread对象相关联。
Thread类中常见到的有关Runnable接口的构造方法:
方法 | 功能描述 |
---|---|
public Thread(Runnable target) | 创建一个新的线程对象(参数是一个Runnable实例) |
public Thread(Runnable target, String name) | 创建一个名称为name的线程对象(其中一个参数是Runnable实例) |
可以使用上面的两个构造方法将Runnable实例与Thread实例相关联。
使用Runnable接口开启新的线程的步骤是什么?
- 建立Runnable对象
- 使用参数为Runnable对象的构造方法创建Thread实例
- 调用start()方法开启线程
具体操作:
首先,编写一个实现Runnable接口的类,然后实例化该类的对象,建立Runnable对象;
接下来使用相应地构造方法创建Thread实例;
最后使用该实例调用Thread类中的start方法启动线程。
代码实现
package cn.wells;
import java.awt.Container;
import java.net.URL;
import javax.swing.*;
public class SwingAndThread extends JFrame{
private JLabel jl = new JLabel();
private static Thread t;
private int count = 0;
private Container container = new Container();
public SwingAndThread() {
setBounds(300, 200, 250, 100);
container.setLayout(null);
URL url = SwingAndThread.class.getResource("./images/1.gif");
Icon icon = new ImageIcon(url);
jl.setIcon(icon);
jl.setHorizontalAlignment(SwingConstants.LEFT);
jl.setOpaque(true);
//定义内部类,实现Runnable接口
t = new Thread(new Runnable() {
public void run() {
//重写run()方法
while(count <= 200) {
jl.setBounds(count, 10, 200, 50);
try {
Thread.sleep(1000);
//设置休眠1000毫秒
} catch(Exception e) {
e.printStackTrace();
}
count += 4;
if(count == 200) {
count = 10;
}
}
}
});
//启动线程
t.start();
container.add(jl);
setVisible(true);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}
public static void main(String[] args) {
new SwingAndThread(); //实例化线程对象
}
}
线程的生命周期
什么是线程的生命周期,为什么有线程的生命周期?
生命周期表示线程的状态。
生命周期有几种状态?
出生状态、就绪状态、运行状态、等待状态、休眠状态、阻塞状态和死亡状态。
如何解释线程的几种状态?
-
出生状态(NEW):出生状态就是线程被创建时处于的状态,在用户使用该线程实例调用start()方法之前线程都是处于出生状态
-
就绪状态(RUNNALBE):
- 当用户调用start()方法后,线程处于就绪状态,也可以叫做可执行状态;
- 当前线程sleep()结束;
- 其他线程join()结束;
- 等待用户输入完毕
- 某个线程拿到了对象锁
-
运行状态(RUNNING):当线程得到系统资源后就进入了运行状态。
-
等待状态(WAITING):
- 当处于运行状态下的线程调用Thread类中的wait()方法时,该线程就进入了等待状态。
- Thread类调用join()方法,该线程进入等待状态
- LockSupport#park
-
休眠状态(TIMED_WAIITNG):当线程调用Thread类中的sleep()方法时,会进入休眠状态,和等待状态类似,但是有一定的时间。
-
阻塞状态(BLOCKED):
- 如果一个线程发出输入输出请求,该线程进入阻塞状态,再其等待输入\输出结束时线程进入就绪状态。
- 在等待获得monitor lock锁过程中,比如等待进入synchronized修饰的代码块或者方法
-
死亡状态(TERMINATED):当线程的run()方法执行完毕时,线程进入死亡状态。
各种状态需要注意什么?
一旦线程进入可执行状态,它会再就绪状态和运行状态下转换,同时也可能进入等待、休眠、阻塞或死亡状态。
进入等待状态的线程必须调用Thread类中的notify()方法才能被唤醒,而notifyAll()方法是将所有处于等待状态下的线程唤醒。
对于阻塞的线程来说,即使系统资源空闲,线程依然不能回到运行状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iNX5m0E0-1589446873673)(…/…/%E6%8A%80%E6%9C%AF%E5%AD%A6%E4%B9%A0/%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E5%A4%9A%E7%BA%BF%E7%A8%8B.assets/1589082022797.png)]
系统调用和状态之间有什么关系?
虽然多线程看起来同时执行,但实际上同一时间点只有一个线程被执行,只是线程之间的切换速度比较快,所以让人感觉产生的线程是同时进行的。再Windows操作系统中,系统回为每个线程分配一小段CPU时间片,一旦CPU时间片结束就会将当前线程换位下一个线程,即使该线程没有结束。
线程所在的最多的状态是就绪状态,就绪状态进入非运行状态常用的方法:
- sleep()方法 -> 进入休眠状态
- wait()方法 -> 进入等待状态
- 等待输入\输出完成
进入运行状态常用的方法:
- notify()方法 -> 指定等待状态线程进入运行状态
- notifyAll()方法 -> 所有等待状态线程进入运行状态
- interrupt()方法
- 线程的休眠时间结束
- 输入\输出结束
操作线程的方法
操作线程的方法主要解决的是什么问题?
这些方法主要的用处是使线程从一个状态过渡到另一种状态。
线程的休眠
使用的方法是sleep()方法,使用sleep()方法需要一个参数用于指定该线程休眠的时间,时间的单位是毫秒。
sleep()方法主要在哪里使用?
sleep()方法通常在run()方法内的循环中使用。因为sleep()方法的使用可能会抛出InterruptedException异常,所以我们常把sleep()方法放在try-catch代码块中。
代码实现
try {
Thread.sleep(2000);
} catch(Exception e) {
e.printStackTrace();
}
注意:使用sleep()方法的线程在一段时间后会醒来,但是并不能保证醒来之后会进入到运行状态,只可以保证进入就绪状态。
根据系统计时器和调度器的精度和准确性,使当前执行的线程休眠(暂时停止执行)指定的毫秒数。但是注意,休眠期间线程并不会失去任何监视器的所有权。
sleep多参数方法表示当前线程会沉睡多久,沉睡时不能释放锁资源,所以沉睡时,其他线程时无法得到锁的。最终调用的其实是单参数的sleep()方法。
代码实现
package cn.wells;
import java.awt.*;
import java.util.Random;
import javax.swing.*;
public class SleepMethodTest extends JFrame{
private Thread t;
//定义颜色数组
private static Color[] color = {Color.BLACK, Color.BLUE, Color.CYAN, Color.GREEN, Color.ORANGE, Color.YELLOW, Color.RED, Color.PINK, Color.GRAY};
private static final Random random = new Random();
//获取随机颜色方法
private static Color getC() {
return color[random.nextInt(color.length)];
}
public SleepMethodTest() {
t = new Thread(new Runnable() {
int x = 30;
int y = 50;
public void run() { //覆盖线程接口run()方法
while(true) { //无限循环
try {
Thread.sleep(100); //线程休眠100毫秒
} catch(Exception e) {
e.printStackTrace();
}
//获取组件绘图上下文对象
Graphics graphics = getGraphics();
graphics.setColor(getC()); //设置绘图颜色
graphics.drawLine(x, y, 100, y++);
if(y >= 80) {
y = 50;
}
}
}
});
t.start();
}
public static void init(JFrame frame, int width, int height) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
public static void main(String[] args) {
init(new SleepMethodTest(), 100, 100);
}
}
线程的加入
线程的加入要使用什么方法?
使用join()方法
使用join()方法是特点?
当一个线程使用join()方法加入到另一个线程时,另一个线程会等待该线程执行完毕后再继续执行。
建议应用程序不要再线程实例中使用wait,notify或者notifyAll
代码实现
package cn.wells;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
public class JoinTest extends JFrame{
private Thread threadA;
private Thread threadB;
final JProgressBar progressBar = new JProgressBar();
final JProgressBar progressBar2 = new JProgressBar();
int count = 0;
public JoinTest() {
super();
getContentPane().add(progressBar, BorderLayout.NORTH);
//将进度条设置在窗体最北面
getContentPane().add(progressBar2, BorderLayout.SOUTH);
progressBar.setStringPainted(true); //设置进度条可现实数字字符
progressBar2.setStringPainted(true);
threadA = new Thread(new Runnable() {
int count = 0;
public void run() { //重写run()方法
while(true) {
progressBar.setValue(++count); //设置进度条当前值
try {
Thread.sleep(100); //线程A休眠时间为100毫秒
threadB.join(); //在线程B中调用join()方法
} catch(Exception e) {
e.printStackTrace();
}
}
}
});
threadA.start(); //启动线程A
threadB = new Thread(new Runnable() {
int count = 0;
public void run() {
while(true) {
progressBar2.setValue(++count);
try {
Thread.sleep(100); //线程B休眠时间为100毫秒
} catch(Exception e) {
e.printStackTrace();
}
if(count == 100) { //当count增长到100时跳出循环
break;
}
}
}
});
threadB.start(); //启动线程B
}
public static void init(JFrame frame, int width, int height) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
public static void main(String[] args) {
init(new JoinTest(), 100, 100);
}
}
线程的中断
用什么方法中断线程?
在run()方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止。
代码实现
package cn.wells;
public class InterruptedTest implements Runnable{
private boolean isContinue = false;
public void run() {
while(true) {
if(isContinue) { //当isContinue值为true时,退出无限循环
break;
}
}
}
public void setContinue() {
this.isContinue = true; //定义设置isContinue变量为ture的方法
}
}
什么情况下使用interrupet()方法,如何使用?
线程可以通过sleep()方法或wait()方法进入就绪状态,然后使用interrupt()方法使线程离开run()方法,同时离开线程,但是这个过程中可能会有InterruptedException异常,所以用户使用的时候在处理异常时完成线程的中断业务处理,如终止while循环。
除非当前线程是中断自身(这时始终允许的),否则将调用此线程的checkAccess方法,这可能导致抛出SecurityException
如果线程被Object类的wait(),wait(long),or wait(long, int),sleep(long),or sleep(long, int)调用而堵塞,让线程进入了等待状态(WAITING)或休眠状态(TIMED_WAITING),而打断这些线程,那么就会抛出InterrputedException.
如果这个线程在一个InterruptibleChannel的I/O操作中被阻塞,主动打断当前线程,那么这个通道将被关闭,线程的中断状态被设置,线程将收到一个ClosedByInterruptException。
如果这个线程在Selector中被阻塞,那么这个线程的中断状态将被设置,并且它将从选择的操作立即返回,可能带有一个非负值,就像调用了选择器的wakeup方法一样。
中断非活动的线程是不会有任何的影响的。
最终调用的其实是native方法
interrupted()方法的使用?
测试当前线程是否被中断。通过此方法可以清除线程的中断状态。换句话说,如果要连续两次调用此方法,则第二个将返回false(除非在第一个调用清除了之后且在第二个调用对其进行检查之前,当前线程再次被中断)。
由此方法返回false,因此将反映线程的中断,因为此线程在中断时尚未处于活动状态而被忽略。
代码实现
package cn.wells;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
public class InterruptedSwing extends JFrame{
Thread thread;
public InterruptedSwing() {
super();
final JProgressBar progressBar = new JProgressBar(); //创建进度条
getContentPane().add(progressBar, BorderLayout.NORTH); //将进度条放在窗口合适位置
progressBar.setStringPainted(true); //设置进度条上显示数字
thread = new Thread(new Runnable() {
int count = 0;
public void run() {
while(true) {
progressBar.setValue(++count); //设置进度条的当前值
try {
thread.sleep(100); //使线程休眠100毫秒
} catch(Exception e) {
//捕获InterruptedException异常
System.out.println("当前程序被中断");
break;
}
}
}
});
thread.start(); //启动进程
thread.interrupt(); //中断进程
}
public static void init(JFrame frame, int width, int heigth) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, heigth);
frame.setVisible(true);
}
public static void main(String[] args) {
//因为使用了interrupt()方法,所以抛出了InterruptedException异常
init(new InterruptedSwing(), 100, 100);
}
}
线程的礼让
什么使线程的礼让?
线程的礼让就是当前正在运行的线程的一个提醒,告知它可以让资源礼让给其他线程。这其实只是一种暗示,没有任何一种机制保证当前线程会让资源礼让。
让步不是绝对不执行,重新竞争时,CPU也有可能还重新选择中自己。
如何实现资源的礼让?
Thread类提供了一种礼让的方法,可以用yield()方法表示。
Thread类中的yield()方法是如何工作的?
yield()方法使具有相同优先级的线程有进入可执行状态的机会,当当前线程放弃执行权时会再度回到就绪状态。对于支持多任务操作系统来说,例如:windows操作系统。不需要调用yield()方法,因为操作系统回为线程自动分配CPU时间片来执行。
令当前线程做出让步,放弃当前CPU,让CPU重新选择线程,避免线程长时占用CPU。
在写while死循环时,预计短时间内while死循环可以结束的话,可在其中使用yield方法,防止CPU一直被占用。
线程的优先级
线程的优先级有什么作用?
每个线程都有各自的优先级,线程的优先级可以表明在程序中执行线程的重要性,如果有许多线程处于就绪状态,系统会根据优先级来决定哪个线程进入运行状态。
但是这个不是绝对性的,不意味着优先级低的线程得到不运行,这只是概率的问题,比如垃圾处理器的优先级就比较低。
线程的优先级如何表示?
Thread类中包含的成员变量提供了线程的某些优先级。如:Thread.MIN_PRIORITY(常数1)、Thread.MAX_PRIORITY(常数10)、Thread.NORM_PRIORITY(常数5) 。每个线程的优先级都在Thread.MIN_PRIORITY~Thread.MAX_PRIOTIRY之间。在默认情况下,线程的优先级是Thread.NORM_PRIORITY。
其中,要注意,每一个新产生的线程都继承了父线程的优先级。
各个线程依据优先级是如何工作的呢?
在多任务操作系统中,每个线程都会得到一小段CPU时间片运行,在事件结束时,将轮换另一个线程进入运行状态,这时系统会选择与当前线程优先级相同的线程予以运行。系统始终选择就绪状态下优先级较高的线程进入运行状态。
如何调节线程的优先级?
线程的优先级可以使用setPriority()方法调整,如果使用该方法设置的优先级不在1~10之间,将产生IllegalArgumentException异常。
代码实现
package cn.wells;
import java.awt.*;
import javax.swing.*;
public class PriorityTest extends JFrame{
Thread threadA; //创建线程
Thread threadB;
Thread threadC;
Thread threadD;
public PriorityTest() {
setLayout(new FlowLayout());
//布局设置
JProgressBar progressBar = new JProgressBar(); //创建进度条对象
JProgressBar progressBar2 = new JProgressBar();
JProgressBar progressBar3 = new JProgressBar();
JProgressBar progressBar4 = new JProgressBar();
threadA = new Thread(new MyThread(progressBar)); //实例化线程
threadB = new Thread(new MyThread(progressBar2));
threadC = new Thread(new MyThread(progressBar3));
threadD = new Thread(new MyThread(progressBar4));
//这段代码有错误:因为我已经进行了布局设置
getContentPane().add(progressBar, FlowLayout.LEFT);
getContentPane().add(progressBar2, FlowLayout.LEFT);
getContentPane().add(progressBar3, FlowLayout.LEFT);
getContentPane().add(progressBar4, FlowLayout.LEFT);
progressBar.setStringPainted(true); //设置进度条上显示数字
progressBar2.setStringPainted(true);
progressBar3.setStringPainted(true);
progressBar4.setStringPainted(true);
setPriority("threadA", 5, threadA); //设置线程优先级
setPriority("threadB", 5, threadB);
setPriority("threadC", 4, threadC);
setPriority("threadD", 3, threadD);
}
private final class MyThread implements Runnable {
private final JProgressBar bar;
int count = 0;
private MyThread(JProgressBar bar) {
this.bar = bar;
}
public void run() {
while(true) {
bar.setValue(count += 10);
try {
Thread.sleep(1000);
} catch(Exception e) {
System.out.println("当前线程被中断");
}
}
}
}
public static void setPriority(String threadName, int priority, Thread t) { //获取优先级
t.setPriority(priority);
t.setName(threadName);
t.start();
}
public static void init(JFrame frame, int width, int heigth) { //实现窗体
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, heigth);
frame.setVisible(true);
}
public static void main(String[] args) {
init(new PriorityTest(), 100, 100);
}
}
线程同步
为什么要使用线程同步?
在单线程中,每做一件事情,后面的事情需要等待前面的事情完成后才可以进行,但是如果使用多线程程序,就会发生两个线程抢占资源的问题,像两个人说话,两个人同时过同一个独木桥等问题。
所以,在多线程编程中需要防止这些资源访问的冲突。Java提供了线程同步的机制来防止资源访问的冲突。
线程安全
什么是线程的安全?
比如在银行排号系统、火车站售票系统中,多线程开发会出现许多的问题,比如有负数、或者买到了同一张票的矛盾。线程安全问题实际上就是来源于两个线程同时存取单一对象的数据。
代码实现
package cn.wells;
public class ThreadSafeTest implements Runnable{
int num = 10;
public void run() {
while(true) {
if(num > 0) {
try {
Thread.sleep(1000);
} catch(Exception e) {
e.printStackTrace();
}
System.out.println("tickets" + num--);
}
}
}
public static void main(String[] args) {
ThreadSafeTest t = new ThreadSafeTest();
Thread tA = new Thread(t);
Thread tB = new Thread(t);
Thread tC = new Thread(t);
Thread tD = new Thread(t);
tA.start();
tB.start();
tC.start();
tD.start();
//结果会出现负数的问题,因为4个线程都抢占了资源,但是资源已经大于了10,所以会出现-1的情况
}
}
线程同步机制
如何解决资源共享的问题呢?
基本上所有解决多线程资源冲突问题的方法都是采用给定时间只允许一个线程访问共享资源,这时就需要给共享资源上一把锁。这就好像一个人上洗手间,他进入洗手间会将门锁上,出来时再将锁打开,然后其他人才可以进入。
同步块
在Java中提供了同步机制,可以有效防止资源冲突,同步机制使用synchronized关键字。
代码实现
package cn.wells;
public class ThreadSafeTest implements Runnable{
int num = 10;
public void run() {
while(true) {
synchronized("") {
if(num > 0) {
try {
Thread.sleep(1000);
} catch(Exception e) {
e.printStackTrace();
}
System.out.println("tickets" + num--);
}
}
}
}
public static void main(String[] args) {
ThreadSafeTest t = new ThreadSafeTest();
Thread tA = new Thread(t);
Thread tB = new Thread(t);
Thread tC = new Thread(t);
Thread tD = new Thread(t);
tA.start();
tB.start();
tC.start();
tD.start();
}
//运行结果中没有出现负数的情况,这是因为将资源放置在了同步块中
}
同步块又称为临时块,它使用了synchronized关键字建立,基本的语法是:
synchronized(Object) {}
解释同步块的工作机制
通常把共享资源放置在syschronized定义的区域内,这样当其他线程获取这个锁时,必须等待锁被释放时才能进入该区域。其中的Object为任意一个参数,每个对象都存在一个标志位,并且由两个值,分别是0和1.一个线程运行到同步块时首先检查该对象的标志位,如果为0状态,表示此同步块中存在其他线程正在运行。这是该线程处于就绪状态,直到处于同步块中的线程执行完同步块中的代码为止。这是该对象的标志位被设置为1,该线程才能执行同步块中的代码,并将Object对象的标志位值设为0,防止其他线程执行同步块中的代码。
同步方法
同步方法就是在方法前面修饰synchronized关键字的方法,其语法如下:
synchronized void f() {}
同步方法的工作机制?
当某个对象调用了同步方法时,该对象上的其他方法必须等待该同步方法执行完毕之后才能被执行。必须将这个能访问共享资源的方法修饰为synchronized,否则会报错。
代码实现
package cn.wells;
public class SynchronizedMethod implements Runnable{
int num = 10;
public synchronized void doit() {
if(num > 0) {
try {
Thread.sleep(1000);
} catch(Exception e) {
e.printStackTrace();
}
System.out.println("tickets" + --num);
}
}
public void run() {
while(true) {
doit();
}
}
public static void main(String[] args) {
SynchronizedMethod t = new SynchronizedMethod();
Thread tA = new Thread(t);
Thread tB = new Thread(t);
Thread tC = new Thread(t);
Thread tD = new Thread(t);
tA.start();
tB.start();
tC.start();
tD.start();
}
}
拓展
什么是守护线程?
在工作中,我们可能会写一些工具做一些监控的工作,这时我们都是用守护线程做的,这样即使监控抛出异常,也不会影响到业务主线程,所以JVM也无需关注监控是否正在进行,该退出时就退出,所以对业务不会产生任何影响。
如何设置守护线程?
创建的线程默认都是非守护线程。
创建守护线程时需要把Thread的daemon属性设置为true,可以通过setDaemon方法进行操作。
守护线程的优先级很低,当JVM退出时,是不关心有无守护线程的,即使还有很多守护线程,JVM依然会退出。