【Java】多线程
多线程实现
继承Thread类实现多线程
class MyThread extends Thread{
private String title;
public MyThread(String title){
this.title = title;
}
@Override
public void run(){
for (int i = 0;i < 10;i++){
System.out.println(title + ",i = " + i);
}
}
}
public class Hello {
public static void main(String[] args) throws Exception{
MyThread my1 = new MyThread("thread1");
MyThread my2 = new MyThread("thread2");
my1.start();
my2.start();
}
}
多线程的启动必须通过调用start方法。
实现Runnable接口实现多线程
class MyThread2 implements Runnable{
private String title;
public MyThread2(String title){
this.title = title;
}
@Override
public void run(){
for (int i = 0;i < 10;i++){
System.out.println(title + ",i = " + i);
}
}
}
public class Hello {
public static void main(String[] args) throws Exception{
MyThread2 my3 = new MyThread2("thread3");
MyThread2 my4 = new MyThread2("thread4");
new Thread(my3).start();
new Thread(my4).start();
}
}
Runnable接口没有了继承的局限性,但是该接口并没有start函数,所以无法直接调用,就需要利用Thread提供的构造方法来调用。
public Thread(Runnable target);
同时这里可以使用内部类和lambda表达式。
Thread与Runnable关系
实际上Thread也是实现了Runnable的接口,并覆盖了run方法。这内部牵扯到代理模式。
Callable实现多线程
如果依靠Runnable接口,中的run方法没有返回值。如果需要返回值,就只能依靠Callable来实现多线程。
public interface Callable<V>{
public V call() throws Exception;
}
class MyThread implements Callable<String> {
private int ticket = 10;
@Override
public String call() throws Exception {
while(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "还有" + ticket-- + "张票");
}
return "结束";
}
}
public class Hello {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<String> task = new FutureTask<>(new MyThread());
new Thread(task).start();
System.out.println("返回数据:" + task.get());
}
}
发现可以使用泛型,这个泛型就是返回值的类型。
线程状态转换图
多线程常用的操作方法
线程名命和获取
public Thread(Runnable terget,String name);//使用构造方法命名
public final synchronized void setName(String name);//使用一般方法命名
public final String getName();//获取线程名字
在Thread类里有个currentThread()函数可以夺取当前线程对象。
public static native Thread currentThread();
class MyThread2 implements Runnable{
@Override
public void run(){
for (int i = 0;i < 10;i++){
System.out.println(Thread.currentThread().getName()+",i="+i);
}
}
}
public class Hello {
public static void main(String[] args) throws Exception{
MyThread2 my = new MyThread2();
new Thread(my).start();
new Thread(my).start();
new Thread(my,"hh").start();
}
}
sleep方法
class MyThread implements Runnable{
@Override
public void run(){
for(int i=0;i<1000;i++){
try {
Thread.sleep(1000);
}catch(InterruptedException e){
e.printstackTrace();
}
}
}
}
该方法会让线程进入休眠。等时间过后才恢复执行。
线程休眠会交出CPU,让CPU执行其他任务。但sleep后不会释放锁。
yield方法
class MyThread implements Runnable {
@Override
public void run() {
for(int i = 0;i < 3;i++){
Thread.yield();
System.out.println(Thread.currentThread().getName());
}
}
}
该方法会让线程让步,会暂停当前在执行的线程,并执行其他线程。
与sleep方法类似,同样不会释放锁。
yield不能控制交出CPU的时间。并且yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
yield不会让线程阻塞,而是让线程回到Runnable状态(就绪状态)
join方法
class MyThread implements Runnable {
@Override
public void run() {
try {
System.out.println("开始");
Thread.sleep(3000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class Hello {
public static void main(String[] args) throws Exception{
MyThread my = new MyThread();
Thread thread = new Thread(my,"子线程");
thread.start();
thread.join();
System.out.println("结束");
}
}
等待该线程终止。
在主线程中调用后,会先让祖贤成进入休眠,直到调用join的线程run方法执行完毕,才会继续执行主线程。
线程停止
1:使用一个标记位,通过判断让线程运行完停止。
2:调用stop方法退出。(该方法不安全,一般不用)
为什么不安全:stop方法会在任意时刻完全停止,并解除该线程获得的锁。所以可能会导致线程同步的混乱问题。
3:使用Thread类中的一个interrupt()中断线程
线程优先级
优先级越高的线程越有可能执行。
public final void setPriority(int new Priority);//Thread类下的设置优先级
public final int getPriority();//获取优先级
Thread类提供了几个常量:
- 最高优先级:public final static int MAX_PRIORITY=10;
- 中等优先级:public final static int NORM_PRIORITY=5;
- 最低优先级:public final static int MIN_PRIORITY=1;
主方法只是一个中等优先级。
线程是有继承关系的,比如当A线程中启动B线程,那么B和A的优先级将是一样的。
守护线程
守护线程是一种特殊的线程,它属于是一种陪伴线程。
简单点说java中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
典型的守护线程就是垃圾回收线程。只要当前VM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当最后一个非守护线程结束时,守护线程才会随着JVM一同停止工作。
使用setDaemon(bool isDaemon);来设置当前线程是否为守护线程。
该方法必须在start之前执行
线程的同步和死锁
synchronized处理同步问题
同步代码块
synchronized (同步对象){
同步代码操作;
}
一般进行同步推向处理的时候可以采用当前对象this进行同步。
class MyThread implements Runnable{
private int ticket = 10;
@Override
public void run(){
for (int i = 0;i < 100;i++) {
System.out.println(i + "--");
synchronized (this) {//表示程序逻辑上上锁
if (this.ticket > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + ticket--);
}
}
}
}
}
同步方法
class MyThread implements Runnable{
private int ticket = 10;
@Override
public void run(){
for (int i = 0;i < 100;i++) {
sale();
}
}
public synchronized void sale(){
synchronized (this) {//表示程序逻辑上上锁
if (this.ticket > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + ticket--);
}
}
}
}
wait()方法和notfiy()方法
wait方法
wait方法使当前执行代码的线程停止运行,并释放对象锁。该方法是Object类的方法。
特点:
- wait()使当前线程调用该方法后进行等待,并且将该线程置入锁对象的等待队列中,直到接到通知或被中断为止。
- wait()方法只能在同步方法中或同步块中调用。
- 如果调用wait()时,没有持有适当的锁,就会抛出异常。
- wait()方法执行后,当前线程释放锁,线程与其它线程竞争重新获取锁。
- wait()之后的线程继续执行有两种方法:
- 调用该对象的notify()唤醒等待线程
- 线程等待时调用interrupt()中断该线程
notify()方法
notify方法使停止的线程继续运行。
1.方法notify()也要在同步方法或同步块中调用。
用来通知可能在等待该对象的对象锁的其它线程,使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑选出一个呈wait状态的线程。
2.在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
class MyThread implements Runnable{
private boolean flag;
private Object obj;
public MyThread(boolean flag,Object obj){
super();
this.flag = flag;
this.obj = obj;
}
public void waitMethod(){
synchronized (obj){
try{
while(true){
System.out.println("wait-begin:" + Thread.currentThread().getName());
obj.wait();
System.out.println("wait-end:" + Thread.currentThread().getName());
return;
}
} catch (Exception e){
e.printStackTrace();
}
}
}
public void notifyMethod(){
synchronized (obj){
try {
System.out.println("notify-begin:" + Thread.currentThread().getName());
obj.notify();
System.out.println("notify-end:" + Thread.currentThread().getName());
} catch (Exception e){
e.printStackTrace();
}
}
}
public void run(){
if(flag){
waitMethod();
}else {
notifyMethod();
}
}
}
public class Hello {
public static void main(String[] args) throws InterruptedException{
Object object = new Object();
MyThread waitThread = new MyThread(true,object);
MyThread notifyThread = new MyThread(false,object);
Thread thread1 = new Thread(waitThread,"wait线程");
Thread thread2 = new Thread(notifyThread,"notify线程");
thread1.start();
Thread.sleep(1000);
thread2.start();
System.out.println("main-end");
}
}
//结果:
//wait-begin:wait线程
//main-end
//notify-begin:notify线程
//notify-end:notify线程
//wait-end:wait线程
notifyAll()方法
notify只是唤醒一个等待的线程。使用notifyAll可以唤醒所有等待的线程。
小结
出现阻塞的情况大体分为如下5种:
- 线程调用sleep方法,主动放弃占用的处理器资源。
- 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
- 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
- 线程等待某个通知。
- 程序调用了suspend方法将该线程挂起。此方法容易导致死锁尽量避免使用该方法。
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。