目录
进程和线程
进程是一个应用程序;线程是应用程序中的执行单元。
一个进程可以由多个线程,最基本的包括主线程和垃圾回收线程。
一个线程占一个栈空间,不同栈空间相互独立,互不共享,堆和方法区内存是对这些栈是共享的。JVM内存结构
所以可以看出,main函数(主线程)结束不代表线程结束,因为还有其它分支线程进行弹栈压栈操作。
线程的创建方法
1.创建一个Thread类的子类,并重写run()方法
2.实现Runnable接口,重写run()方法,并用new Thread(实现接口的对象)进行封装
3.使用匿名类的形式快速实现Runnable接口,并进行封装
线程的运行状态
五种状态:新建状态——就绪状态——运行状态——阻塞状态——死亡状态。
new Thread()标志着为新建状态;
start()标志着进入就绪状态,又叫做可执行状态,拥有抢夺CPU时间片的权利(执行权),当抢到了一块CPU时间片之后,就开始执行run方法;
run()标志着进入运行状态,当这个时间片用完之后会再次回到就绪状态,然后重新抢夺时间片并继续run方法的运行(run的内容不会再次从头开始运行);
遇到阻塞事件(用户输入或者sleep)标志着进入阻塞状态,放弃当前的时间片,回到就绪状态重新夺取时间片;
run()的内容运行完之后就进行死亡状态。
线程的sleep,interrupt和终止线程
sleep()函数是静态方法,作用是对当前线程(所处的代码块决定)进行休眠。interrupt()函数是非静态方法,可以中断线程中的休眠时间这种方式是通过Java的异常处理机制来实现的。
真正意义上的线程终止是通过添加一个布尔标记来终止线程的。
......
rm.run=false;//终止t4线程
......
class MyRunnable implements Runnable{
boolean run=true;//布尔标记
@Override
public void run() {
// TODO Auto-generated method stub
if (run) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}else {
//save
return;
}
}
}
多线程并发
真正的多线程并发是指同一时间点有多个线程同时运行。对于单核CPU一次只能运行一个线程,系统会对多个线程进行调度,在线程之间来回切换,在宏观上看来像是在同时运行。
并发: 当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。
并行: 当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
package birda;
//创建线程的三种方法
public class Mythread extends Thread{
@Override
public void run() {
for(int i=0;i<1000;i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//继承Thread
Mythread t1=new Mythread();
t1.setName("t1线程");
t1.start();
for(int i=0;i<1000;i++) {
System.out.println("主线程--->"+i);
}
try {
Thread.sleep(1000*5);//当前线程睡眠;此时当前线程为主线程
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("开始执行t2线程的内容");
//实现Runnable接口
Thread t2=new Thread(new MyRunnable());
t2.setName("t2线程");
t2.start();
try {
Thread.sleep(1000*5);//当前线程睡眠;此时当前线程为主线程
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("开始执行t3线程的内容");
//匿名类的形式创建线程
Runnable r=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
try {
Thread.sleep(1000 * 60);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
}
System.out.println("已经被睡眠60s了");
if (Thread.interrupted()) {
System.out.println("线程被中断过");
}
}
};
Thread t3=new Thread(r);
t3.setName("线程t3");
System.out.println("线程t3的名字为: "+t3.getName());
t3.start();
//中断线程睡眠
t3.interrupt();
//终止线程
//通过布尔标记的方式终止
MyRunnable rm=new MyRunnable();
Thread t4=new Thread(rm);
t3.setName("线程t4");
t3.start();
//主线程睡眠5秒
try {
Thread.sleep(100*5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
rm.run=false;//终止t4线程
}
}
class MyRunnable implements Runnable{
boolean run=true;
@Override
public void run() {
// TODO Auto-generated method stub
if (run) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}else {
//save
return;
}
}
}
线程调度
1.线程调度模型
抢占式调度模型:优先级高的线程抢夺的时间片大概率是多的(并非所有时候都这样)【Javja采用的调度机制】
均分式调度模型:每一个线程所占用的时间片的多少是相等的。
2.Java中的调度方法
实例方法:
getPriority()和setPriority()分别为得到线程优先级和设置优先级
join()是让当前线程进入阻塞状态,先运行调度该方法的线程
静态放法
yield()是让当前线程从运行状态进入就绪状态
package birda;
public class Mythread extends Thread{
@Override
public void run() {
// Thread.currentThread().yield();
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
@SuppressWarnings("static-access")
public static void main(String[] args) {
// TODO Auto-generated method stub
//继承Thread
Thread t1=new Mythread();
t1.setName("t1线程");
t1.start();
//设置优先级
for(int i=0;i<10;i++) {
Thread.currentThread().yield();//线程让位一次,从运行状态进入就绪状态
System.out.println("主线程--->"+i);
}
try {
Thread.sleep(1000*2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(t1.getPriority());
//创建第二个线程
Runnable r=new MyRunnable();
Thread t2=new Thread(r);
t2.setName("t2线程");
t2.start();
System.out.println(t2.getPriority());
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
Thread t3=new Mythread();
t3.setName("t3线程");
t3.start();
try {
t3.join();//当前线程受阻塞,先运行t3再运行其它线程
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i+" ");
//System.out.println(Thread.currentThread().getName());
}
}
}
线程并发带来的安全问题
线程产生安全问题的原因是什么:两个线程a,b同时访问一个数据m。b应该在a执行完成之后访问m。但是如果ab是并发的,线程a会改变m的值,存在一种可能就是线程b访问的是最开始的那个m。
存在线程安全问题的三个条件:
1.线程是并发的
2.共享数据
3.数据有修改的行为
看下面的程序代码:
public class Threadsafe2 extends Thread{
private Account account;
public Threadsafe2(Account t) {
this.account=t;
}
@Override
public void run() {
account.withdraw(5000);
System.out.println(Thread.currentThread().getName()+":取款金额为:"+5000+" 余额:"+account.getbalance());
}
public static void main(String[] args) {
Account account=new Account("account-001",10000);
Threadsafe2 t1=new Threadsafe2(account);
t1.setName("线程t1");
t1.start();
Threadsafe2 t2=new Threadsafe2(account);
t2.setName("线程t2");
t2.start();
}
}
//另一块代码
public class Account {
private String account;
private int accountmoney;
public Account(String name,int money) {
this.account=name;
this.accountmoney=money;
}
//取钱
public void withdraw(int m) {
int before=accountmoney;
int after =before-m;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.getmoney(after);
}
//显示账户余额
public void getmoney(int n) {
accountmoney=n;
}
public int getbalance() {
return accountmoney;
}
}
线程t1:取款金额为:5000 余额:5000
线程t2:取款金额为:5000 余额:5000
线程同步
线程同步机制就是线程按照先后顺序执行,这样会牺牲一部分的效率;
线程异步就是多个线程直接按相互独立,执行过程互不影响,就是线程并发。
synchronized代码块
synchronized代码块的用法是占用一个对象锁,只让当前占用这个锁的线程运行,其他用到这个锁的线程排队等待。
synchronized(m){
//m是需要排队的线程的共享变量
}
当m为this的时候:
//整体代码与上面相同,修改的部分如下:
public void withdraw(int m) {
synchronized (this) {
int before = accountmoney;
int after = before - m;
try {
Thread.sleep(100);//睡眠的目的是不及时更新余额,制造安全问题
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.getmoney(after);
}
}
//执行结果
t1:取款金额为:5000 余额:5000
t2:取款金额为:5000 余额:0
this在这里面的指的是Account account=new Account("account-001",10000);
每一个对象有一把锁,在这里面account是被t1和t2共享的,那么只能有一个线程占用这把锁,当这个所被占用后,其他线程就需要等待。
锁池lockpool:线程进入锁池找共享对象的对象锁,此时会释放之前占有的CPU时间片,接下来有两种可能,如果找到了就进入就绪状态重新抢夺时间片,如果没找到那就在锁池中继续等待。
当m为成员变量时
public void withdraw(int m) {
synchronized (account) {
int before = accountmoney;
int after = before - m;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.getmoney(after);
}
}
//执行结果
t1:取款金额为:5000 余额:5000
t2:取款金额为:5000 余额:0
成员变量account是属于共享对象Account account=new Account("account-001",10000);
的,而这个对象是共享的,那么account也是被共享的。
当m为常量时
public void withdraw(int m) {
synchronized ("123") {
int before = accountmoney;
int after = before - m;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.getmoney(after);
}
}
//执行结果
t1:取款金额为:5000 余额:5000
t2:取款金额为:5000 余额:0
当m为字符串常量时一定会发生线程同步,并且是对全部线程都是同步的,因为字符串常量“123”只有一把锁。当m为静态成员变量原理也是一样的。
静态变量和字符串常量都是存放在方法区中的
当m为局部变量时
首先需要知道,局部变量随着函数每一次调用都是被重新创建。
public void withdraw(int m) {
String a=new String();
//如果是String s=“123”的s也是局部变量,但是其内存是在方法区中的,会共享同一块内存
//所以会发生线程同步现象
synchronized (a) {
int before = accountmoney;
int after = before - m;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.getmoney(after);
}
}
//执行结果
t2:取款金额为:5000 余额:5000
t1:取款金额为:5000 余额:5000
Java中的三大变量
实例变量:堆中(共享)
静态变量:方法区中(共享)
局部变量:栈中(不共享),永远不会发生线程安全问题。
扩大同步
public void run() {
synchronized (account) {
account.withdraw(5000);
System.out.println(Thread.currentThread().getName() + ":取款金额为:" + 5000 + " 余额:" + account.getbalance());
}
}
synchronized用在实例方法上面
此时只能锁的是this,不灵活,同步的是整个方法,无故扩大同步范围,影响执行效率,优点:代码节俭
用法:
public synchronized void withdraw(int m) {
String a=new String();
int before = accountmoney;
int after = before - m;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.getmoney(after);
}
总结
1.synchronized只是对大括号里面的代码同步执行,其余代码正常执行。
2.锁更像是一个内存,当这个内存被一个线程占用了,那么其他线程就得排队。
3.StringBuffer,Vector,Hashtable都是线程安全的;StringBuilder,ArrayList,HashMap,HashSet都是线程不安全的。
线程安全的类
1.HashMap和HashTable都实现了Map接口,但是HashMap不是线程安全的类,HashTable是线程安全的类
2.StringBuffer是线程安全的类,适合在多线程情况下进行字符串拼接;StringBuilder不是线程安全的类,在单线程的时候,StringBuilder比StringBuffer要快
3.Vector是线程安全的,ArrayList不是线程安全的
4.可以通过Collection操作将非线程安全的类转换成线程安全的类
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = Collections.synchronizedList(list1);
线程池
当有很多个线程需要启动和结束的时候,会导致系统变慢!所以引入线程池的设计思想!
1.创建一个任务容器
2.一次性启动十个线程
3.这是个线程都处于wait状态
4.有一个“任务”被扔到线程中来,那么就有一个线程被唤醒
5.被唤醒的线程执行完这个“任务”之后,继续等待下一个“任务”
6.如果有多个“任务”,就有多个线程被唤醒
当任务被执行完毕后,线程并不会结束,继续循环已经存在的线程!
手写线程池代码:万物皆对象
1.创建一个ThreadPool的类,里面要有装线程的一个容器,容器里面的类是要继承Thread的;
2.容器里面的线程是干什么用的,用来处理接收到的任务,添加任务需要有一个add函数
3.线程在ThreadPool对象的时候就应该被启动好了,所以得有一个方法,用来启动线程
4.线程启动,代表里面run被执行,线程不会关闭,所以while(true),因为对于容器对象来说,添加任务和执行任务会产生安全问题
看代码:
package lockpool;
import java.util.LinkedList;
public class ThreadPool {
// 线程池大小
int threadPoolSize;
// 任务容器
LinkedList<Runnable> tasks = new LinkedList<Runnable>();
// 试图消费任务的线程
public ThreadPool() {
threadPoolSize = 10;
// 启动10个任务消费者线程
synchronized (tasks) {
for (int i = 0; i < threadPoolSize; i++) {
new TaskConsumeThread("任务消费者线程 " + i).start();
}
}
}
public void add(Runnable r) {
synchronized (tasks) {
tasks.add(r);
// 唤醒等待的任务消费者线程
tasks.notifyAll();
}
}
class TaskConsumeThread extends Thread {
public TaskConsumeThread(String name) {
super(name);
}
Runnable task;
public void run() {
System.out.println("启动: " + this.getName());
while (true) {
synchronized (tasks) {
while (tasks.isEmpty()) {
try {
tasks.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
task = tasks.removeLast();
// 允许添加任务的线程可以继续添加任务
tasks.notifyAll();
}
System.out.println(this.getName() + " 获取到任务,并执行");
task.run();
}
}
}
}
public class TestA {
public static void main(String[] args) {
ThreadPool pool = new ThreadPool();
for (int i = 0; i < 5; i++) {
Runnable task = new Runnable() {
@Override
public void run() {
//System.out.println("执行任务");
//任务可能是打印一句话
//可能是访问文件
//可能是做排序
}
};
pool.add(task);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
java自带的线程池
ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
第一个参数: 10代表线程池的默认容量
第二个参数: 15代表最大容量,当任务超过10个之后,会把容量扩充至15
第三个参数: 结合第四个参数TimeUnit.SECONDS,表示经过60秒,多出来的线程还没有接到活儿,就会回收,最后保持池子里就10个
第五个参数: new LinkedBlockingQueue() 用来放任务的集合
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
threadPool.execute(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("任务1");
}
});
}
lock对象
lock是一个接口,声明一个接口类的对象:
Lock lock = new ReentrantLock();
占用锁:lock.lock();
一旦占用不会自动释放,需要自己手动释放:lock.unlock();(放在finally中进行);
Lock lock = new ReentrantLock();
Thread t1 = new Thread() {
public void run() {
try {
log("线程启动");
log("试图占有对象:lock");
lock.lock();
log("占有对象:lock");
log("进行5秒的业务操作");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log("释放对象:lock");
lock.unlock();
}
log("线程结束");
}
};
t1.setName("t1");
t1.start();
trylock
trylock执行的就是在规定的时间内去抢占锁,所以存在没有占用成功的现象,所以在释放锁的时候需要一个标记来判断!
Lock lock = new ReentrantLock();
Thread t1 = new Thread() {
public void run() {
boolean locked = false;
try {
log("线程启动");
log("试图占有对象:lock");
locked = lock.tryLock(1,TimeUnit.SECONDS);
if(locked){
log("占有对象:lock");
log("进行5秒的业务操作");
Thread.sleep(5000);
}
else{
log("经过1秒钟的努力,还没有占有对象,放弃占有");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(locked){
log("释放对象:lock");
lock.unlock();
}
}
log("线程结束");
}
};
t1.setName("t1");
t1.start();
lock对象的对象交互
通过调用lock对象的newCondition方法的返回一个Condition对象,通过调用这个对象的await,signal,signalAll方法去实现线程交互
public class Mylock {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// TODO Auto-generated method stub
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
log("t1成功抢占锁");
log("t1执行五秒钟的操作");
log("t1执行暂停,并且释放锁");
try {
condition.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// condition.signal();
log("t1又开始执行");
}
});
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
log("t2成功抢占锁");
log("t2执行五秒钟的操作");
// condition.signal();
log("t2执行完成");
condition.signalAll();
lock.unlock();//这一句必须加上,因为signal只是唤醒,并没有释放锁
}
});
t2.start();
}
public static void log(String str) {
System.out.println(new Date() + str);
}
}
原子访问
原子访问是指一条语句执行不可拆分,不可中断的操作!比如赋值语句·int a=5
但是i++
不是原子语句,分为三个原子性操作在一起的,1.取i的值;2.i加1;3.赋值给i;所以这就有可能会产生线程安全问题!
java包里有一个把一些操作封装成了原子性操作!java.util.concurrent.atomic
public static void main(String[] args) throws InterruptedException {
AtomicInteger atomicI =new AtomicInteger();
int i = atomicI.decrementAndGet();
int j = atomicI.incrementAndGet();
int k = atomicI.addAndGet(3);
}
同步测试:分别使用基本变量的非原子性的++运算符和 原子性的AtomicInteger对象的 incrementAndGet 来进行多线程测试。
package mythread;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
public class Mylock {
private static int values = 0;
private static AtomicInteger atomicValue = new AtomicInteger();
public static void main(String[] args) {
Thread[] ts1 = new Thread[100000];
for (int i = 0; i < 100000; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
values++;
}
});
t.start();
ts1[i] = t;
}
// 等待线程结束
for (int i = 0; i < 100000; i++) {
try {
ts1[i].join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(values);
Thread[] ts2 = new Thread[100000];
for (int i = 0; i < 100000; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
atomicValue.incrementAndGet();
}
});
t.start();
ts2[i] = t;
}
// 等待线程结束
for (int i = 0; i < 100000; i++) {
try {
ts2[i].join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(atomicValue.intValue());
}
}
死锁
死锁现象的产生是由于共享对象被两个线程占用没有得到释放,程序无法执行下一步,运行不会出错,会永远僵持在那里。synchronized的嵌套使用比较容易造成这种现象。
package threadsafe;
public class Threadsafe1 {
public static void main(String[] args) {
Object o1=new Object();
Object o2=new Object();
MyThreada m1=new MyThreada(o1,o2);
MyThreadb m2=new MyThreadb(o1,o2);
m1.setName("m1");
m2.setName("m2");
m1.start();
m2.start();
}
}
class MyThreada extends Thread{
private Object o1;
private Object o2;
public MyThreada(Object o1,Object o2) {
this.o1=o1;
this.o2=o2;
}
@Override
public void run() {
synchronized(o1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(o2) {
System.out.println(Thread.currentThread().getName());
}
}
}
}
class MyThreadb extends Thread{
private Object o1;
private Object o2;
public MyThreadb(Object o1,Object o2) {
this.o1=o1;
this.o2=o2;
}
@Override
public void run() {
synchronized(o2) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(o1) {
System.out.println(Thread.currentThread().getName());
}
}
}
}
开发中如何解决线程安全问题
1.尽量用局部变量代替实例变量和静态变量
2.如果必须是实例变量,考虑尽量多创建几个对象
3.用synchronized
守护线程
Java语言中的线程分为两大类,一类是用户线程,一类是守护线程,例如:main线程就是用户线程,垃圾回收线程就是守护线程。
守护线程的特点:一般是死循环,随着用户线程的结束而结束;用来每隔一段时间做某一件事,这也可以设置一个定时器,并且将定时器设置为守护线程
实现守护线程
语法:线程.setDaemon(true);
public class Threadsafe3 {
public static void main(String[] args) {
BakThread b=new BakThread();
b.setName("备份线程");
b.setDaemon(true);
b.start();
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class BakThread extends Thread{
@Override
public void run() {
int i=0;
while(true) {
System.out.println(Thread.currentThread().getName()+"--->"+i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//执行结果
main-->0
守护线程--->0
守护线程--->1
main-->1
main-->2
守护线程--->2
守护线程--->3
main-->3
守护线程--->4
main-->4
守护线程--->5
main-->5
守护线程--->6
main-->6
main-->7
守护线程--->7
main-->8
守护线程--->8
main-->9
守护线程--->9
守护线程--->10
定时器
作用:间隔特定的时间去执行特定的程序
三种实现方法:
1.设置线程睡眠时间(最原始)
2.java.util.Timer是封装好的定时器,可以直接用,也很少用,因为很多框架自带定时器
3.Spring框架中SpringTask里面有定时器
实现定时器
Timmer t=new Timer();
t.schedule(TimerTask task,起始时间,间隔时间);
//TimerTask implements Runnable
//所以任务代码可以是Runna类
public class Threadsafe4 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Timer t=new Timer(true);//true是把定时器设置为守护线程,默认是false
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" );
try {
Date firsttime=sdf.parse("2020-10-11 05:48:00");
t.schedule(new MyThreadsafe4(),firsttime,1000);
//也可以用匿名类的形式
//new TimerTask(){重写run方法};
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class MyThreadsafe4 extends TimerTask{
public void run() {
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" );
String s=sdf.format(new Date());
System.out.println(s+"数据保存了");
}
}
//执行结果
main--->0
2020-10-11 15:59:59数据保存了
main--->1
2020-10-11 16:00:00数据保存了
main--->2
2020-10-11 16:00:01数据保存了
main--->3
2020-10-11 16:00:02数据保存了
main--->4
2020-10-11 16:00:03数据保存了
main--->5
2020-10-11 16:00:04数据保存了
main--->6
2020-10-11 16:00:05数据保存了
main--->7
2020-10-11 16:00:06数据保存了
main--->8
2020-10-11 16:00:07数据保存了
main--->9
2020-10-11 16:00:08数据保存了
2020-10-11 16:00:09数据保存了
实现线程的第三种方式:FutureTask,实现Callable接口(JDK8新特性)
这种方式实现的线程得到线程完成之后的返回值。用这种方式可以拿到实现结果
语法:
FutureTask task=new FutureTask(Callable接口的实现类对象); Thread t=new Thread(task);t.start();Object o=task.get()
public class Threadsafe5 {
public static void main(String[] args) {
@SuppressWarnings("unchecked")
FutureTask task=new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName()+"--->begain");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"--->end");
int a=5;
int b=10;
return a+b;//自动装箱
}
});
Thread t=new Thread(task);
t.setName("未来线程");
t.start();
try {
Object o=task.get();//会阻塞主线程
System.out.println(o);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//执行结果
未来线程--->begain
未来线程--->end
15
缺点: FutureTask.get()会导致当前线程阻塞,效率比较低。
wait() 和notify()
这两个方法是Object 的方法,所以所有类都有这个方法。
Object o=new Obejct();o.wait()
wait()方法让执行o上的线程进入无期限等待状态。
执行o上的线程就是上面代码块所在的线程。
o.notify()
是随机唤醒一个执行o上面的线程。
notifyall()
唤醒在此对象上的所有等待的线程
wait()和notify()方法是建立在线程同步的基础上的。o.wait()方法会让当前线程进入等待状态,并让当前线程释放之前占用o的对象锁;notify()方法只会通知(可以执行了),不会释放之前占用o的对象锁
生产者和消费者模式
这里面用trylock去实现生产者和消费者模式!
问题
什么时候开始抢夺时间片呢?
start()之后,run()之前。