多线程学习
多线程入门学习
线程创建的两种方式
继承Thread类
实现Runnable接口
比较:
- Runnable方式可以避免Thread方式由于java单继承特性带来的缺陷
- Runnable的代码可以被多个线程(Thread实例)共享,适合与多个线程处理统一资源的情况
火车票例子
package demo.thread;
class MyThread extends Thread {
private int ticketsCont = 5;//一共有5张火车票
private String name;//窗口,也即是线程的名字
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
while(ticketsCont > 0) {
ticketsCont--;//如果还有票,就卖掉一张
System.out.println(name + "卖了1张票,剩余票数为:" + ticketsCont);
}
}
}
public class TicketsThread {
public static void main(String[] args) {
//创建三个线程,模拟三个窗口
MyThread mt1 = new MyThread("窗口1");
MyThread mt2 = new MyThread("窗口2");
MyThread mt3 = new MyThread("窗口3");
//启动三个线程,开始买票
mt1.start();
mt2.start();
mt3.start();
}
}
本来只有5张票,现在每个线程都有5张票,一共变成15张了,这不是我们想要的结果,改成实现Runnable接口就可以解决!
package demo.runnable;
class MyThread implements Runnable {
private int ticketsCont = 5;//一共有5张火车票
@Override
public void run() {
while(ticketsCont > 0) {
ticketsCont--;//如果还有票,就卖掉一张
System.out.println(Thread.currentThread().getName() + "卖了1张票,剩余票数为:" + ticketsCont);
}
}
}
public class TicketsRunnable {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread th1 = new Thread(mt, "窗口1");
Thread th2 = new Thread(mt, "窗口2");
Thread th3 = new Thread(mt, "窗口3");
//启动线程,开始买票
th1.start();
th2.start();
th3.start();
}
}
线程的生命周期
- 创建:new一个线程
- 就绪:创建了线程对象后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取cpu服务,具备了运行的条件,但不一定开始运行!)
运行:处于就绪状态的的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑 - 终止:线程的run()方法执行完毕,或者线程调用了stop()方法(这种方法被淘汰掉了),线程便进入终止状态
- 阻塞:一个正在运行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法
如何停止线程
使用 stop() 方法强行终止线程,但是不推荐使用这个方法,该方法已被弃用。
如何中断线程
使用 interrupt 方法中断线程,调用 interrupt() 方法仅仅是在当前线程中打一个停止的标记,并不是真的停止线程。也就是说,线程中断并不会立即终止线程,而是通知目标线程,有人希望你终止。至于目标线程收到通知后会如何处理,则完全由目标线程自行决定。
public class InterruptThread extends Thread{
public static void main(String[] args) {
try {
InterruptThread t = new InterruptThread();
t.start();
Thread.sleep(3000);
t.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
super.run();
for(int i = 0; i <= 100000; i++) {
System.out.println("i=" + i);
}
}
}
notify、join、yield的方法说明
join:join是Thread的实例方法,让指定的线程先执行完再执行其他线程,而且会阻塞主线程
notify:notify是Object的方法,唤醒一个waiting态的线程,这个线程呢,必须是用同一把锁进入waiting态的
yield:yield是Thread的静态方法,在某个线程里调用Thread.yield(),会使这个线程由正在运行的running状态转变为等待cpu时间片的runable状态。
线程的异常处理
在Java中,线程方法的异常(无论是checked还是unchecked exception),都应该在线程代码边界之内(run方法内)进行try catch并处理掉。一个异常被抛出后,如果没有被捕获处理,则会一直向上抛。异常一旦被Thread.run() 抛出后,就不能在程序中对异常进行捕获,最终只能由JVM捕获。
class MyThread extends Thread{
public void run(){
System.out.println("Throwing in " +"MyThread");
throw new RuntimeException();
}
}
class Main {
public static void main(String[] args){
MyThread t = new MyThread();
t.start();
try{
Thread.sleep(1000);
}
catch (Exception x){
System.out.println("Caught it" + x);
}
System.out.println("Exiting main");
}
}
死锁的解决方案
死锁
两个或多个线程互相持有对方需要的锁而导致这些线程全部处于永久阻塞状态。如:线程A持有对象1的锁,等待对象2的锁;线程B持有对象2的锁,等待对象1的锁。
例子
package com.sxt.syn;
/*
* 死锁:过多的同步可能造成相互不释放资源
* 从而相互等待,一般发生于同步中持有多个对象的锁
* 避免:不要在同一个代码块中,同时持有多个对象的锁
*/
public class DeadLock {
public static void main(String[] args) {
Markup g1=new Markup(1,"美女");
Markup g2=new Markup(0,"妹子");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class Markup extends Thread{
static Lipstick lipstick=new Lipstick();//用static实现多个对象共用同一份资源。
static Mirror mirror=new Mirror();//用static实现多个对象共用同一份资源。
//选择
int choice;
//名字
String girl;
public Markup(int choice,String girl) {
this.choice=choice;
this.girl=girl;
}
@Override
public void run() {
//化妆
markup();
}
//相互持有对方的对象锁-->可能造成死锁
private void markup() {
if(choice==0) {
synchronized(lipstick) {
//获得口红的锁
System.out.println(this.girl+"涂口红");
//1秒后获得镜子的锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*synchronized(mirror) {
System.out.println(this.girl+"照镜子");
}*/
}
//将代码挪下来,不要锁套锁就不会造成死锁
synchronized(mirror) {
System.out.println(this.girl+"照镜子");
}
}
else {
synchronized(mirror) {
//获得镜子的锁
System.out.println(this.girl+"照镜子");
//2秒后获得口红的锁
try {
Thread.sleep(2000);//这里故意实现时间间隔,才可能造成死锁
} catch (InterruptedException e) {
e.printStackTrace();
}
/*synchronized(lipstick) {
System.out.println(this.girl+"涂口红");
}*/
}
synchronized(lipstick) {
System.out.println(this.girl+"涂口红");
}
}
}
}
解决方案
不要在同一个代码块中,同时持有多个对象的锁
线程的守护神----守护线程
java线程有两类
用户线程
简介:运行在前台,执行具体的任务。程序的主线程,连接网络的子线程等都是用户线程
守护线程
简介:运行在后台,为其他前台线程服务
特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作
应用
数据库连接池中的监测线程、jvm启动后的检测线程
最常见的守护线程:垃圾回收线程
如何设置守护线程
可以通过调用Thread类的setDaemon(true)来设置当前的线程为守护线程
注意事项:
- setDaemon(true)方法必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常
- 在守护线程产生的新线程也是守护线程
- 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
实例
package demo.daemon;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Scanner;
class DaemonThread implements Runnable {
@Override
public void run() {
System.out.println("进入守护线程" + Thread.currentThread().getName());
try {
writeToFile();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("退出守护线程" + Thread.currentThread().getName());
}
private void writeToFile() throws Exception {
File fileName = new File("d:" + File.separator + "daemon.txt");
OutputStream os = new FileOutputStream(fileName, true);
int count = 0;
while(count < 999) {
os.write(("\r\nword" + count).getBytes());
System.out.println("守护线程" + Thread.currentThread().getName()
+ "向文件写入了word" + count++);
Thread.sleep(1000);
}
}
}
public class DaemonThreadDemo {
public static void main(String[] args) {
System.out.println("进入主线程" + Thread.currentThread().getName());
DaemonThread daemonThread = new DaemonThread();
Thread thread = new Thread(daemonThread);
thread.setDaemon(true);
//守护线程开始写文件
thread.start();
//阻塞主线程
Scanner scanner = new Scanner(System.in);
scanner.next();
//主线程执行完后,守护线程没执行完也得退出!
System.out.println("退出主线程" + Thread.currentThread().getName());
}
}
jstack生成线程快照
作用:生成jvm当前时刻线程的快照(threaddump,即是当前线程中所有线程信息)
目的:帮助定位程序问题出现的原因,如长时间停顿、cpu占用率过高等
使用:
cmd进入java安装目录的bin下,使用jstack [-l] pid
-l:打印锁的一些信息,可不加。
pid:进程id,可通过任务管理器查看javaw.exe(class运行中才出现)的pid
详解synchronized关键字
作用
能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果
不使用并发手段的后果
package demo.runnable;
public class DisappearRequest1 implements Runnable {
static DisappearRequest1 instance = new DisappearRequest1();
static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
//由于t1和t2的join(),main线程要等待t1和t2执行完才继续执行
System.out.println(i);
}
@Override
public void run() {
for(int j = 0; j < 100000; j++) {
i++;
}
}
}
两个线程同时i++,最后结果会比预计的少
原因:i++,看上去是一个操作,实际上包含三个操作。读取i,将i加1,将i的值写入到内存中
两种用法
对象锁
包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)
锁对象
可以锁this,也可以创建一个对象,然后锁定这个对象
package demo.runnable;
public class SynObjectCodeBlock2 implements Runnable {
static SynObjectCodeBlock2 instance = new SynObjectCodeBlock2();
Object lock = new Object();
Object lock1 = new Object();
Object lock2 = new Object();
@Override
public void run() {
//synchronized(this) { //lock或者this效果一样
/*synchronized(lock) {
System.out.println("我是对象锁的代码块形式,我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}*/
synchronized(lock1) {
System.out.println("我是lock1,我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "lock1运行结束");
}
synchronized(lock2) {
System.out.println("我是lock2,我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "lock2运行结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}
锁对象办法
synchronized修饰普通方法,锁对象默认为this
package demo.runnable;
public class SynObjectMethod3 implements Runnable {
static SynObjectMethod3 instance = new SynObjectMethod3();
@Override
public void run() {
//由于方法加了synchronized,
//一个线程要等待另一个线程执行完才能执行这个办法
method();
}
public synchronized void method() {
System.out.println("我是对象锁的方法修饰符形式,我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}
类锁
概念
指synchronized修饰静态的方法或指定锁为Class对象(java类可能有很多个对象,但是只有一个Class对象)
本质
就是Class对象的锁而已
效果
类锁只能在同一时刻被一个对象拥有(不同的Runnable实例对应的类锁只有一个)
使用方式
synchronized加在静态方法上
package demo.runnable;
/**
* 类锁的第一种方式,static形式
*/
public class SynClassStatic4 implements Runnable {
//注意两个实例
static SynClassStatic4 instance1 = new SynClassStatic4();
static SynClassStatic4 instance2 = new SynClassStatic4();
@Override
public void run() {
method();
}
public static synchronized void method() {
System.out.println("我是类锁的第一种方式->static形式,我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}
synchronized(*.class){}
package demo.runnable;
/**
* 类锁的第2种方式,synchronized(*.class){}
*/
public class SynClassClass5 implements Runnable {
//注意两个实例
static SynClassClass5 instance1 = new SynClassClass5();
static SynClassClass5 instance2 = new SynClassClass5();
@Override
public void run() {
method();
}
public void method() {
synchronized(SynClassClass5.class) {
System.out.println("我是类锁的第2种方式->synchronized(*.class){},我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}
多线程访问同步方法的7种情况(面试常考)
1.两个线程同时访问一个对象的的同步(普通)方法:能锁住,不同时发生
2. 两个线程同时访问两个对象的的同步(普通)方法:不能锁住,同时发生
3.两个线程访问的是synchronized的静态方法:能锁住
4.同时访问同步方法和非同步方法:非同步方法不受影响
package demo.runnable;
/**
* 同时访问同步办法和非同步方法
*/
public class SynYesandNo6 implements Runnable {
static SynYesandNo6 instance = new SynYesandNo6();
@Override
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")) {
method1();
} else {
method2();
}
}
public synchronized void method1() {
System.out.println("我是加锁,我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public void method2() {
System.out.println("我是没加锁,我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}
5.访问同一个对象的不同的普通同步方法
package demo.runnable;
/**
* 同时访问同步办法和非同步方法
*/
public class SynDifferentMethod7 implements Runnable {
static SynDifferentMethod7 instance = new SynDifferentMethod7();
@Override
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")) {
method1();
} else {
method2();
}
}
public synchronized void method1() {
System.out.println("我是方法1,我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public synchronized void method2() {
System.out.println("我是方法2,我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}
6.同时访问静态synchronized和非静态synchronized方法:不能锁住,可以同时发生
package demo.runnable;
/**
* 同时访问静态同步办法和普通同步方法
*/
public class SynStaticAndNormal8 implements Runnable {
static SynStaticAndNormal8 instance = new SynStaticAndNormal8();
@Override
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")) {
method1();
} else {
method2();
}
}
public static synchronized void method1() {
System.out.println("我是静态加锁方法1,我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public synchronized void method2() {
System.out.println("我是非静态加锁方法2,我叫" +
Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()) {
}
System.out.println("finished");
}
}
7.方法抛出异常后,会释放锁
总结
1.一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况)
2.每个实例都对应有自己的一把锁,不同实例之间互不影响;例如:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象共用同一把类锁(对应第2、3、4、6种情况);
3.无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)