目录
使用实现Runnable接口创建线程的方式(存在线程安全问题)
一、线程的使用
Thread类的有关方法
1、start():启动当前线程;调用当前线程的run()方法 2、run():通常需要重写Thread类中的run()方法,建创建的线程执行的操作声明在此方法中 3、currentThread():静态方法,返回执行当前代码的线程 4、getName():获取当前线程的名字 5、setName():设置当前线程的名字 6、yield():释放当前CPU执行权 7、join():在线程A中,调用线程B的join()方法,此时线程A就进入阻塞状态,直到线程B执行完后,线程A才结束阻塞状态 8、stop():强制结束当前线程(已过时) 9、sleep():让当前线程阻塞一段时间(单位:毫秒) 10、isAlive():判断当前线程是否存活
线程的调度
调度策略:
- 时间片
- 抢占式:优先级高的线程抢占CPU
java的调度方法
- 同优先级线程组先进先出队列,使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
线程的优先级
线程的优先等级
> MAX_PRIORITY:10 > MIN_PRIORITY:1 > NORM_PRIORITY:5--->默认优先级
如何获取优先级
> getPriority():获取当前线程的优先级 > setPriority(int newPriority):设置线程的优先级
说明:优先级高的要先抢占线程的执行权,但只是从概率上讲,优先级高的线程高概率情况下被执行,但并不意味着只有当优先级高的线程执行完以后,低优先级的线程才开始执行
线程有关方法及线程优先级练习
class ThreadMethod extends Thread{
@Override
public void run() {
for(int i=0;i<=25;i++){
if(i%2==0){
try {
//阻塞一秒
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName()+":"+getPriority()+":"+i);
}
if(i%20==0){
this.yield();
}
}
}
public ThreadMethod(String name){
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
ThreadMethod m1=new ThreadMethod("分线程1");
// m1.setName("线程1");
//设置分线程的优先级
m1.setPriority(Thread.MAX_PRIORITY);
m1.start();
//给主线程命名
Thread.currentThread().setName("主线程");
//设置主线程优先级
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for(int i=0;i<=25;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+":"+i);
}
if(i==20){
try {
m1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
运行结果如下:
线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用 thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
Java垃圾回收就是一个典型的守护线程。
若JVM中都是守护线程,当前JVM将退出。
二、线程的生命周期
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
- 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:当新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时该线程具备了运行条件,只是没分配到CPU资源
- 运行:当处于就绪状态的线程被调度并获得CPU资源时,便进入运行状态,run()定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人认为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了自己的全部工作或线程被提前强制性的终止或出现异常导致结束
三、线程的同步(一)(线程安全问题)
以具体例题来说明线程安全问题
模拟火车站售票程序,开启三个窗口售票,总票数为100张
使用继承Thread类创建线程的方式(存在线程安全问题)
public class WindowTest1 {
public static void main(String[] args) {
Window w1=new Window();
Window w2=new Window();
Window w3=new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread{
private static int ticket=100;
@Override
public void run() {
while(true){
if(ticket>0){
System.out.println(getName()+":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
部分运行结果:
使用实现Runnable接口创建线程的方式(存在线程安全问题)
public class WindowTest2 {
public static void main(String[] args) {
Window2 w=new Window2();
Thread thread1 = new Thread(w);
Thread thread2 = new Thread(w);
Thread thread3 = new Thread(w);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
class Window2 implements Runnable{
private int ticket=100;
@Override
public void run() {
while(true){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+",卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
部分运行结果如下:
1. 多线程出现了安全问题
2. 问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有 执行完,另一个线程参与进来执行。导致共享数据的错误。
3. 解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以 参与执行。
synchronized的使用方法
java对于多线程的安全问题提供了专业的解决放方式:同步机制
1、同步代码块
synchronized(同步监视器){
//需要被同步的代码;
}
说明:
- 操作共享数据的代码,即为需要被同步的代码
- 共享数据:多个线程共同操作的变量
- 同步监视器,俗称:锁
- 任何对象都能充当锁
- 多个线程必须共享同一把锁
使用同步代码块解决上述线程安全问题
使用继承Thread类的方式
public class WindowTest1 {
public static void main(String[] args) {
Window w1=new Window();
Window w2=new Window();
Window w3=new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread{
private static int ticket=100;
private static Object obj1=new Object();
@Override
public void run() {
while(true){
//方式二:synchronized (obj1){
synchronized (Window.class){
//类也是对象 相当于 Class clazz=Window.class
//Window.class只会加载一次
if(ticket>0){
System.out.println(getName()+":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
}
使用实现Runnable接口的方式
public class WindowTest2 {
public static void main(String[] args) {
Window2 w=new Window2();
Thread thread1 = new Thread(w);
Thread thread2 = new Thread(w);
Thread thread3 = new Thread(w);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
class Window2 implements Runnable{
Object obj=new Object();
private int ticket=100;
@Override
public void run() {
while(true){
synchronized (this){ //此时的this:唯一的window2的对象 //方式二:synchronized(obj) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ",卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
2、同步方法
如果操作共享数据的代码完整的声明在一个方法中,不妨将这个方法声明为同步的
说明:
- 同步方法仍然涉及到同步监视器,只是不需要显式的声明
- 静态同步方法中,同步监视器是:this(默认)
- 非静态方法中,同步监视器是:当前类本身
使用同步方法块解决上述线程安全问题
使用继承Thread类的方式
public class WindowTest1_2 {
public static void main(String[] args) {
Window1_2 w1=new Window1_2();
Window1_2 w2=new Window1_2();
Window1_2 w3=new Window1_2();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
class Window1_2 extends Thread{
private static int ticket=100;
@Override
public void run() {
while(true){
show();
}
}
private static synchronized void show(){//同步监视器:t1,t2,t3(此种方法是错误的)
//必须保证此方法是静态的
//此时的同步监视器:Window1_2
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}
}
}
使用实现Runnable接口的方式
public class WindowTest2_2 {
public static void main(String[] args) {
Window2_2 w=new Window2_2();
Thread thread1 = new Thread(w);
Thread thread2 = new Thread(w);
Thread thread3 = new Thread(w);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
class Window2_2 implements Runnable{
private int ticket=100;
@Override
public void run() {
while(true){
show();
}
}
private synchronized void show(){//同步监视器:this
if(ticket>0){
System.out.println(Thread.currentThread().getName()+",卖票,票号为:"+ticket);
ticket--;
}
}
}