一、创建线程
1、继承Thread类
- 子类覆写父类中的run方法
- 建立子类对象的同时线程也被创建
- 调用start () 开启线程
public class Test {
public static void main(String[] args) {
2、建立子类对象
Pop p = new Pop();
3、开启线程
p.start();
for (int i = 0; i < 20; i++) {
System.out.println("main" + i);
}
}
}
class Pop extends Thread {
1、重写父类中的run方法
public void run(){
for (int i = 0; i <10 ; i++) {
System.out.println("chi"+ i);
}
}
}
弊端:继承是单继承,不能实现多继承,因此使用 Runnable 接口来实现多继承
2、Runnable接口
- 子类覆盖接口中的run方法
- 通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数
- Thread类对象调用start方法开启线程
- 可使用匿名内部类来写
public class Test {
public static void main(String[] args) {
//1.创建一个Runnable接口的实现类对象
Pop p = new Pop();
//2.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t = new Thread(p);
//3.调用Thread类中的start方法,开启新的线程执行run方法
t.start();
for (int i = 0; i < 50; i++) {
System.out.println("main" + i);
}
}
}
class Pop implements Runnable {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
System.out.println("chi" + i);
}
}
}
3、匿名内部类实现
public class Test {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("nihao");
}
}
}.start();
}
}
lambda表达式
public class Test {
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i <10 ; i++) {
System.out.println("nihao");
}
}).start();
}
}
4、 实现Callable接口,重写call方法
比较
通过继承Thread类和实现Runnable接口来创建线程的缺点:
1)没有返回值
2)不支持泛型
3)异常必须处理
因此,使用Callable接口这种方法功能更加强大 ↓
public class Test2 {
public static void main(String[] args) {
// 计算任务
Compute c = new Compute();
/*
FutureTask同时实现了Runnable,Future接口
它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
可以看成FutureTask实现了Runnable接口
*/
FutureTask<Integer> ft = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
TimeUnit.SECONDS.sleep(10);//10s
return c.add(1, 1);
}
});
new Thread(ft, "A").start();
try {
System.out.println("正在计算结果:");
System.out.println(ft.get());
System.out.println("计算完成了吗?"+ft.isDone());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class Compute {
public int add(int a, int b) {
return a + b;
}
}
当我们卖票的时候,同时有三个售票窗口,相当于三个线程,这时候如果同时卖出第100号票,那就会造成线程不安全问题,如何解决呢?就给CPU抢到的那条线程加上锁,锁住对象,后面的就进不来,直到执行完毕后,将1---->0,锁打开,其他线程继续抢夺执行权,重复操作,保证线程安全。
二、解决线程安全问题
1. 同步代码块
格式:
synchronized (锁对象) { 共享资源 }
public class Test2 {
public static void main(String[] args) {
Run p = new Run();
new Thread(()->{
p.run();
},"A").start();
new Thread(()->{
p.run();
},"B").start();
}
}
-------------------------------------------------------------------------------------
public class Run implements Runnable {
private int tk = 100;
@Override
public void run() {
while(true){
paytk();
}
}
public /*synchronized*/ void paytk() {
synchronized (this) {
if (tk > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->正在卖票第" + tk + "张");
tk--;
}
}
}
}
2. 同步方法
格式:
synchronized 返回值类型 方法名 (参数列表){ 共享资源 }
public synchronized void paytk(){
if(tk>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖票第"+tk+"张");
tk--;
}
}
3. 静态方法的同步
注意:
静态的锁对象不能是this了,
this是创建对象后产生,
静态方法优先对象,
静态方法的锁对象是本类的class属性
public class Run implements Runnable {
private static int tk = 100;
@Override
public void run() {
while (true) {
paytk();
}
}
//静态方法的同步,记得成员变量也要是静态的才行
public static synchronized void paytk() {
if (tk > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->正在卖票第" + tk + "张");
tk--;
}
}
}
-------------------------------------------------------------------------
public static void paytk() {
synchronized (Run.class) {
if (tk > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->正在卖票第" + tk + "张");
tk--;
}
}
}
4. Lock 锁
ReentrantLock 与synchronized 区别
ReentrantLock:
是一个类,可设置。
构造方法,默认的构造是非公平重入锁(false),也可以设置为公平锁。
可以手动释放锁。finally中释放锁。
可以设置超时时间。
比如说:A线程持有锁,B线程可以等待一定的时间之后,
如果得不到锁,就放弃,不会死等。
看代码执行完了,就会释放锁。
synchronized:
是一个关键字,不可以改变。
一旦加上锁,不可以中断。
比如说:A线程持有synchronized锁,那么B线程尝试获得锁,
拿不到的时候就会进入EntryList中等待。
这里是看锁标志位改了没有,没改就认为是锁住的。
public class Run implements Runnable {
private int tk = 100;
1.在成员属性位置创建一个ReentrantLock对象
Lock lc = new ReentrantLock();
@Override
public void run() {
while (true) {
2.在可能出现安全的代码前面调用lock()
lc.lock();
if (tk > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->正在卖票第" + tk + "张");
tk--;
}
3.开锁
lc.unlock();
}
}
}
对于开锁,要实现是否有异常出现,都释放掉锁,提高效率,使用 finally子句
public void run() {
while (true) {
lc.lock();
if (tk > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "-->正在卖票第" + tk + "张");
tk--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 3.开锁
lc.unlock();
}
}
}
}
三、等待唤醒 —— wait、notify
注意:
1、wait和notify方法必须是同一个锁对象调用
2、wait和notify方法都是属于Object类中的方法
3、wait和notify方法必须在同步代码块中使用
生产者与消费者问题
public class Test2 {
public static void main(String[] args) {
Object obj = new Object();
// 消费者排队领票
new Thread() {
@Override
public void run() {
while (true) {
//使用同步保证两个线程只执行一个
synchronized (obj) {
System.out.println("现在排队领票看'你好,李焕英!'");
// 调用wait()
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒之后就可以拿票观看了
System.out.println("好的,我准备观看了!");
System.out.println("----------------------------");
}
}
}
}.start();
// 生产者发票
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//使用同步保证两个线程只执行一个
synchronized (obj) {
System.out.println("拿好票,可以看电影了");
// 唤醒消费者可以领票了
obj.notify();
}
}
}
}.start();
}
}
四、Daemon 守护线程
Java线程分为用户线程和守护线程。
守护线程:处于后台运行,任务是为其他线程提供服务,是程序运行的时候在后台提供一种通用服务的线程。JVM的GC回收线程就是守护线程。
特点:当用户线程执行结束,守护线程就会结束。
设置后台线程:Thread对象setDaemon(true);
注意:
1、setDaemon(true)必须在start()调用前,否则出现IllegalThreadStateException异常;
2、用户线程创建的线程默认是用户线程,守护线程创建的线程也是守护线程;
3、判断是否是守护线程:使用Thread对象的isDaemon()方法。
public class Test2 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
//判断守护线程创建的线程是否为守护线程?
Thread t2 = new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2" + (Thread.currentThread().isDaemon() ? "是守护线程" : "不是守护线程"));
});
t2.start();
int i=1;
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1" + (Thread.currentThread().isDaemon() ? "是守护线程," : "不是守护线程,")+"这是第"+i+"次");
// 当用户线程执行结束,守护线程就会结束。
i++;
if(i >= 5 ){
break;
}
} });
t1.setDaemon(true);
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束了");
}
}
/*t2是守护线程
t1是守护线程,这是第1次
主线程结束了*/