实现多线程的方式:使用一个类继承Thread使类成为一个线程类
实现runnable接口重写run方法,将实现类的子类当target传入Thread(数据是可以共享的)(实现类可以继承)
线程的创建方式比较熟悉的是通过集成Runnable接口或者集成Thread类来实现,但是这两种方法都在线程执行完成后无法获取到执行结果,如果想异步执行某个任务并且执行完成之后需要知道他执行的结果的话可以使用Callable方式实现。
1实现callable接口实现call方法,call方法有返回值,再创建callable的实现类。
2使用futuretask对象包装callable对象,该对象封装了该callable对象的call方法返回值。
3使用futuretask对象的作为thread对象的target
4通过futuretask的对象的get方法获得子线程执行结束后的返回值。
使用start方法不会立即执行,可以在主线程里面加个sleep方法让主线程休息一毫秒,就行了。
其他线程与主线程有同样的地位,主线程结束其他线程不一定会结束。
public class CallableAndFuture {
public static void main(String[] args) {
Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
return new Random().nextInt(100);
}
};
FutureTask<Integer> future = new FutureTask<Integer>(callable);
new Thread(future).start();
try {
Thread.sleep(5000);// 可能做一些事情
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值,那么这个组合的使用有什么好处呢?假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到.
join线程
让一个线程等待另一个线程完成的方法,当执行join时,调用的线程将被阻塞,直到join线程执行完为止。应用场景,将大问题划分成小问题,每个小问题分配线程,所有小问题得到处理后再调用主线程。
可通过setDaemon设置后台线程,必须在start之前,主线程结束,后台线程自动死亡。
sleep与yield方法区别
1.sleep方法执行后会给其他线程机会,不理会其他线程的优先级,但yield方法会给优先级相同或比他高的先执行。
2.sleep方法会将线程转为阻塞状态,yield则进入就绪状态
3.sleep抛出异常,yield方法不抛出任何异常
synchronize同步代码块,格式synchronize(obj){}含义:线程在执行同步代码块之前,必须获得同步监视器的锁定。通过这种方式可以进入修改共享资源的代码区又称临界区,同一时刻只有一个线程处于临界区内。静态方法的话用的是该类的字节码文件。synchronize锁方法的锁为this。
同步锁,reentranlock锁获取锁对象,lock.lock然后unlock
public class deadlock extends Thread{
private boolean flag;
public deadlock(boolean b){
flag=b;
}
public void run(){
if(flag){
synchronized (lock.obja) {
System.out.println("if obja");
synchronized (lock.objb) {
System.out.println("if objb");
}
}
}else{
synchronized (lock.objb) {
System.out.println("else objb");
synchronized (lock.obja) {
System.out.println("else obja");
}
}
}
}
}
生产者消费者部分代码
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
//判断有没有
if(s.flag){
try {
s.wait(); //t1等着,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (x % 2 == 0) {
s.name = "林青霞";
s.age = 27;
} else {
s.name = "刘意";
s.age = 30;
}
x++; //x=1
//修改标记
s.flag = true;
//唤醒线程
s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
}
//t1有,或者t2有
}
}
}
必须为同一把锁
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if(!s.flag){
try {
s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);
//林青霞---27
//刘意---30
//修改标记
s.flag = false;
//唤醒线程
s.notify(); //唤醒t1
}
}
}
}
线程通信:使用同步监视器方法实现通信,显示使用condition通信,使用阻塞对象实现线程通信,
threadlocal
public class pool {
public static ThreadLocal<Integer> x=new ThreadLocal<Integer>(){
protected Integer initialValue() {//匿名内部类初始化值,底层调用的是currentthread线程将自己属性与每一个线程绑定。
return 200;
};
};
public static void main(String[] args) {
// System.out.println(Thread.currentThread().getName()+"--"+x.get());
new Thread(new myrunnable()).start();
new Thread(new myrunnable()).start();
}
public static class myrunnable implements Runnable{
public myrunnable() {
x.set(20);
System.out.println(Thread.currentThread().getName()+"--"+x.get());
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--"+x.get());
}
}
}
happenbefore指令重排,指代码执行顺序与预期不一致。
volatitle保证程序变量的可见性,对线程A修改后,后面的线程可以看到修改的数值。即进行修改后立即写入主内存,线程变量在读取的时候在主内存读而不是在缓存中读。各线程的工作空间彼此不可见,相互独立,在工作时虚拟机为每个线程分配工作空间,不仅包括线程内部的局部变量,也包括线程所需要的共享变量的副本。只保证数据的同步,不保证原子性。
public class pool {
public static volatile int num=0;//不加volatitle永远不会停,加了之后数据立马共享
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
while(num==0){}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
num=11;
}
}
ReentrantLock可重入锁,底层是wait外加锁计数器维持。模拟锁代码。
public class Lock{
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock()
throws InterruptedException{
Thread thread = Thread.currentThread();
while(isLocked && lockedBy != thread){
wait();
}
isLocked = true;
lockedCount++;
lockedBy = thread;
}
public synchronized void unlock(){
if(Thread.currentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
}
使用锁
public class Count{
Lock lock = new Lock();
public void print(){
lock.lock();
doAdd();
lock.unlock();
}
public void doAdd(){
lock.lock();
//do something
lock.unlock();
}
}
我们设计两个线程调用print()方法,第一个线程调用print()方法获取锁,进入lock()方法,由于初始lockedBy是null,所以不会进入while而挂起当前线程,而是是增量lockedCount并记录lockBy为第一个线程。接着第一个线程进入doAdd()方法,由于同一进程,所以不会进入while而挂起,接着增量lockedCount,当第二个线程尝试lock,由于isLocked=true,所以他不会获取该锁,直到第一个线程调用两次unlock()将lockCount递减为0,才将标记为isLocked设置为false。
jvm栈内存是线程独有的,堆内存是共享的。只有一个方法区(字符串常量,静态方法)。