Java中的线程
Java中,线程以对象的形式存在。
Thread表示线程类。
获取线程对象
-
获取当前正在运行的线程对象
//获取当前运行的线程对象 Thread ct = Thread.currentThread();
-
创建一个线程对象
构造方法
常用构造方法 | 说明 |
---|---|
Thread() | 创建一个默认的线程对象 |
Thread(String name) | 创建一个指定线程名的线程对象 |
Thread(Runnable target) | 将一个Runnable对象包装为线程对象 |
Thread(Runnable target,String name) | 将一个Runnable对象包装为线程对象同时命名 |
常用方法
常用方法 | 作用 |
---|---|
setName(String str) | 设置线程名称 |
setPriority(int i) | 设置线程优先级(1~10),数字越大优先级越高,线程越先执行完 |
setDaemon(boolean f) | 是否将该线程设置为守护线程 |
getId() | 获取线程ID |
getName() | 获取线程名,主线程名默认main,自定义线程名默认Thread-N |
getPriority() | 获取线程优先级 |
getState() | 获取线程状态 |
isDaemon() | 判断该线程是否属于守护线程 |
start() | 启动线程 |
run() | 线程启动后执行的方法 |
Thread.currentThread() | 获取当前运行的线程对象 |
Thread.sleep(long m) | 设置当前线程休眠m毫秒 |
Thread.yield() | 线程让步,让其他线程执行 |
实现多线程
方式一:继承Thread类
-
1.让某个类成为Thread类的子类
-
2.重写Thread类中的run方法,将要让该线程执行的内容写在该方法中
-
3.创建Thread类的对象后,调用start()方法,启动线程
自定义线程Thread类的子类
package com.hqyj.ThreadTest; /* * 自定义线程,让其循环打印1~100 * 1.继承Thread类 * 2.重写Thread类中的run方法,将要让该线程执行的内容写在该方法中 * 3.创建Thread类的对象后,调用start()方法,启动线程 * */ public class MyThread extends Thread{ //如果在创建线程对象时需要设置线程名,这里调用父类的构造方法 public MyThread(String name){ super(name); } /* * 希望多线程环境下执行的代码,写在run方法中 * */ @Override public void run() { //获取当前线程对象 Thread mt = Thread.currentThread(); for (int i = 0; i < 100; i++) { //让当前线程休眠,让其他线程有机会在当前线程休眠的时间执行 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(mt.getName()+"--"+i); } } }
main类
package com.hqyj.ThreadTest; public class MainThread { public static void main(String[] args) throws InterruptedException { //先创建自定义线程对象,让该线程启动 Thread mt1 = new MyThread("线程A"); Thread mt2 = new MyThread("线程B"); //不能调用run(),必须要调用start()方法才能启动线程 mt1.start(); mt2.start(); } }
方式二:实现Runnable接口(建议使用)
由于Java中是单继承,所以如果某个类已经使用了extends关键字去继承了另一个类,这时就不能再使用extends继承Thread类实现多线程,就需要使用实现Runnable接口的方法实现多线程。
-
1.自定义一个类,实现Runnable接口
-
2.重写run方法,将要多线程执行的内容写在该方法中
-
3.创建Thread线程对象,将自定义的Runnable接口实现类作为构造方法的参数
-
4.调用线程对象的start()方法启动线程
自定义Runnable接口的实现类
package com.hqyj.RunnableTest; /* * 如果一个类已经使用了extends,就不得不使用implements Runnable实现该接口 * 再通过创建Thread时,将该类进行包装,实现多线程 * */ public class MyRunnableThread implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + i); } } }
main类
package com.hqyj.RunnableTest; public class Main { public static void main(String[] args) { //依然要创建Thread对象才能使用多线程 // 这里用的是Thread(Runnable target)构造方法 // 或Thread(Runnable target,String name)构造方法 //将Runnable接口的实现类对象包装为一个线程对象 MyRunnableThread myRunnableThread = new MyRunnableThread(); Thread t1 = new Thread(myRunnableThread,"线程A"); //调用start()方法时,如果被分配到CPU时间片,会自动执行run()方法 t1.start(); Thread t2 = new Thread(myRunnableThread,"线程B"); t2.start(); } }
方式三:使用匿名内部类
如果不想创建一个Runnable接口的实现类,就可以使用匿名内部类充当Runnable接口的实现类
package com.hqyj.UnnameInnerClassThreadTest; /* * 使用匿名内部类 * 本质还是使用Thread(Runnable target) * 并没有创建一个Runnable接口的实现类,而是直接用一个匿名内部类作为参数,包装成一个线程对象 * */ public class Test { public static void main(String[] args) { //newThread()小括号中的参数就是一个匿名内部类 //创建线程对象,将匿名内部类包装成Thread对象后直接start()启动 new Thread(new Runnable() { //多线程做的事情依然写在run方法中 @Override public void run() { for (int i = 0; i < 100; i++) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + i); } } }, "线程A").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + i); } } }, "线程B").start(); } }
线程的生命周期
线程的初始化到终止的整个过程,称为线程的生命周期
新生状态
当线程对象被实例化后,就进入了新生状态。new Thread()
就绪状态
当某个线程对象调用了start()方法后,就进入了就绪状态。
在该状态下,线程对象不会做任何事情,只是在等待CPU调用。
运行状态
当某个线程对象得到运行的机会后,则进入运行状态,开始执行run()方法。
不会等待run()方法执行完毕,只要run()方法调用后,该线程就会再进入就绪状态。这时其他线程就有可能穿插其中。
阻塞状态
如果某个线程遇到了sleep()方法或wait()等方法时,就会进入阻塞状态。
sleep()方法会在一段时间后自动让线程重新就绪。
wait()方法只有在被调用notify()或notifyAll()方法唤醒后才能进入就绪状态。
终止状态
当某个线程的run()方法中所有内容都执行完,就会进入终止状态,意味着该线程的使命已经完成。
多线程访问同一资源
可能出现的问题
如多线程卖票,多线程取钱,类似的多个线程在操作同一个资源时,实际操作的结果和预想结果不符。
如票实际卖出的大于原本的,取出的钱大于本金等。
出现问题的原因
由于线程调用start()方法后,就进入就绪状态,如果获得了CPU的使用权,开始调用run()方法,调用run方法后,马上就会重新回到就绪状态,所以不会等待run()方法执行结束,在执行run()方法的时候,可能其他线程也可能获取CPU的使用权,从而开始执行run()方法。
当前所有线程是在异步执行。
如何解决
让线程同步执行(排队)即可。就能在某个线程执行run方法的时候,让其他线程等待run()方法执行完毕
synchronized关键字
这个关键字可以修饰方法或代码块。
修饰方法
写在方法的返回值前。这样该方法就称为同步方法,在执行的时候,其他线程要排队等待该方法执行完毕后才执行。
public synchronized void fun(){ //要同步的代码 }
修饰代码块
写在{}前,这样这段{}中的内容称为同步代码块,在执行的时候,其他线程要排队等待该方法执行完毕后才执行。
synchronized(要同步的对象或this){ //要同步的代码 }
原理
每个对象都默认有一把"锁",当某个线程运行到被synchronized修饰的方法时,该对象就会拥有这把锁,在拥有锁的过程中,其他线程不能同时访问该方法,只有等待其执行结束后,才会释放这把锁。
使用synchronized修饰后拥有的锁称为"悲观锁",在拥有的时候,不允许其他线程访问。
方法被synchronized修饰后,成为了同步方法,就会让原本的多线程成为了单线程(异步变为同步),虽然效率变低,但是数据安全是首位。
多线程卖票
package com.hqyj.SellTicket; /* * 定义自动售票机,创建多个线程模拟卖10张票 * */ public class TicketMachine implements Runnable { //假设多线程卖10张票 private int ticket = 10; /* * 卖票的方法 * */ public synchronized void sell() { if (ticket > 0) { System.out.print(Thread.currentThread().getName() + "售出一张"); ticket--; System.out.println(Thread.currentThread().getName() + "剩余" + ticket); } else { System.out.println(Thread.currentThread().getName() + "已售罄"); } } @Override public void run() { while (ticket > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } sell(); } } public static void main(String[] args) { //创建一个售票机对象 TicketMachine ticketMachine = new TicketMachine(); //将其使用3个线程类进行包装,让其在多线程环境下卖票 Thread t1 = new Thread(ticketMachine,"窗口A"); Thread t2 = new Thread(ticketMachine,"窗口B"); Thread t3 = new Thread(ticketMachine,"窗口C"); //线程就绪 t1.start(); t2.start(); t3.start(); //如果没有将卖票的方法sell()使用synchronized修饰, //就有可能在线程A执行的途中,线程B也开始执行, //从而线程A中原本要打印3句话的,只打印了1句,线程B就抢进来执行 //最终导致后续流程出错 //所以对方法sell()使用synchronized修饰后, //在某个线程执行sell()方法时,其他线程就会排队等待其执行完毕 } }
多线程取钱
Account类
package com.hqyj.Withdrawing; /* * 定义一个多线程共同访问的一个类:账户类 * */ public class Account { private double balance; public Account(double balance) { this.balance = balance; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } }
ATM类
package com.hqyj.Withdrawing; public class ATM implements Runnable { private Account account; public ATM(Account account) { this.account = account; } /* * 取钱 * */ public void getMoney(int money) { //同步代码块,在操作account对象时,其他线程不能也同时操作该对象 synchronized (account) { double v = account.getBalance() - money; System.out.println("ATM取出" + money); account.setBalance(v); System.out.println("ATM显示剩余" + account.getBalance()); } } @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } getMoney(5000); } }
Bank类
package com.hqyj.Withdrawing; public class Bank implements Runnable { private Account account; public Bank(Account account) { this.account = account; } /* * 取钱的方法,在操作account对象时,不允许别的线程访问该对象 * */ public void getMoney(int money) { synchronized (account) { double v = account.getBalance() - money; System.out.println("银行取出" + money); account.setBalance(v); System.out.println("银行显示剩余" + account.getBalance()); } } @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } getMoney(5000); } }
main类
package com.hqyj.Withdrawing; public class Main { public static void main(String[] args) { //创建多线程要访问的同一对象 Account account = new Account(10000); //创建两个线程 Thread t1 = new Thread(new ATM(account)); Thread t2 = new Thread(new Bank(account)); //线程就绪 t1.start(); t2.start(); } }
多线程相关面试题
-
实现多线程的方式?
-
继承Thread类
-
实现Runnable接口后,包装为Thread对象
-
使用匿名内部类
-
-
为什么说StringBuilder是非线程安全的
package com.hqyj.ThreadSafeTest; public class Test { public static void main(String[] args) throws InterruptedException { // StringBuilder sb = new StringBuilder(); StringBuffer sb = new StringBuffer(); //模拟10个线程,每个线程都往可变字符串中添加内容 //如果是StringBuilder,最终可能没有添加指定的次数 //如果是StringBuffer,最终一定会添加指定的次数 for (int i = 0; i < 10; i++) { new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 100; j++) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } sb.append("a"); } } }).start(); } Thread.sleep(5000); System.out.println(sb.length()); } }
-
什么叫死锁?怎么产生?如何解决?
如有两个人吃西餐,必须要有刀和叉才能吃饭,只有一副刀叉。
如果A拿到了刀,B拿到了叉,互相都在等待另一个工具,但都不释放自己的,
这时就会造成死锁的局面,既不结束,也不继续。
模拟死锁出现的原因
定义两个线程类,线程A先获取资源A后,再获取资源B;线程B先获取资源B后,再获取资源A。
如果对A和B对使用了synchronized进行同步,就会在线程A获取资源A时候,线程B无法获取资源A,
相反线程B在获取资源B的时候,线程A无法获取资源B,所以两个线程都不会得到另一个资源。
package com.hqyj.DeadLock; public class MyThread1 implements Runnable{ //定义两个成员变量:刀、叉 private Object knife; private Object fork; public MyThread1(Object knife, Object fork) { this.knife = knife; this.fork = fork; } //当前线程执行run方法的顺序是:先获取knife对象,等待3s后获取fork对象 //由于对knife使用了synchronized关键字,表示在使用该knife对象期间,其他线程无权使用 //只有等待同步代码块执行结束后,其他线程才有机会获取knife对象 @Override public void run() { synchronized (knife){ System.out.println(Thread.currentThread().getName()+"获得了刀,3s后获得叉"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (fork){ System.out.println(Thread.currentThread().getName()+"获得了叉,可以吃饭了"); } } } }
package com.hqyj.DeadLock; public class MyThread2 implements Runnable{ //定义两个成员变量:刀、叉 private Object knife; private Object fork; public MyThread2(Object knife, Object fork) { this.knife = knife; this.fork = fork; } //当前线程执行run方法的顺序是:先获取fork对象,等待3s后获取knife对象 //由于对fork使用了synchronized关键字,表示在使用该fork对象期间,其他线程无权使用 //只有等待同步代码块执行结束后,其他线程才有机会获取fork对象 @Override public void run() { synchronized (fork){ System.out.println(Thread.currentThread().getName()+"获得了叉,3s后获得叉"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (knife){ System.out.println(Thread.currentThread().getName()+"获得了刀,可以吃饭了"); } } } }
package com.hqyj.DeadLock; public class Main { public static void main(String[] args) { Object knife = new Object(); Object fork = new Object(); Thread mt1 = new Thread(new MyThread1(knife, fork), "小明"); Thread mt2 = new Thread(new MyThread2(knife, fork), "刘涵"); mt1.start(); mt2.start(); } }
死锁的解决方式一:
让线程A和线程B获取资源A和资源B的顺序保持一致。
如让两个线程都先获取fork,再获取knife
@Override public void run() { synchronized (fork){ System.out.println(Thread.currentThread().getName()+"获得了叉,3s后获得叉"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (knife){ System.out.println(Thread.currentThread().getName()+"获得了刀,可以吃饭了"); } } }
死锁的解决方式二:
让线程A和线程B获取资源A和资源B之前,再获取第三个资源,并对其使用synchronized进行同步,
这样线程A在获取第三个资源后,将所有事情执行完后,线程B才能继续。
如在获取fork和knife之前,先获取paper对象
@Override public void run() { //先获取paper,在拥有paper对象期间,其他线程排队等待 synchronized (paper){ System.out.println(Thread.currentThread().getName()+"获得了餐巾,准备下一步"); synchronized (fork){ System.out.println(Thread.currentThread().getName()+"获得了叉,3s后获得叉"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (knife){ System.out.println(Thread.currentThread().getName()+"获得了刀,可以吃饭了"); } } } }