一.进程与线程
进程就是一个程序(例如打开QQ),线程就是所答开QQ中同时打开多个聊天窗口,各个进程都是独立的,而线程则不同,他们有时是可以进行数据共享的。
多线程就是同时执行多个线程(其实还是处理器逐个去执行)
二.使用多线程
使用多线程最常用的方法有两种:一是直接继承Thread 二是实现runnable接口
继承Thread:
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Test");
}
public static void main(String[] args) {
MyThread mythread = new MyThread();
mythread.start();
}
}
此处要重写run方法,run方法中的程序就是在开启这条线程后要执行的代码块
实现runnable:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Test");
}
public static void main(String[] args) {
Runnable runnable=new MyRunnable();
Thread thread=new Thread(runnable);
thread.start();
}
}
此处也是写run方法的内容
实现runnable方法和Thread方法本质是有很大的区别的:
通过阅读Thread的源码我们可知,它的源码就是在构造时要传入一个runnable的实例对象,传递后在.start时要检查是否传递了runnable对象,如果没有就不能执行。所以直接new Thread的方法就是需要复写这个方法。
如果在构造Thread时候没有传递Runnable,或者没有复写Run那边了或者没有复写Thread的run方法,该Thread不会调用任何东西。使用Thread方法是要写run方法,实际上是在重写他父类中的run方法。
Runnable不是线程,他只是一个传递的接口,只有Thread是线程。
在这里我们还需要注意在创建Thread时有两个需要传入的参数:ThreadGroup stacksize
ThreadGroup:如果构造线程对象时,没有传入ThreadGroup,Thread会默认获取父线程中的ThreadGroup作为他自己的ThreadGroup,此时他和父线程在同一ThreadGroup中。
stacksize:构造Thread时传入stacksize表示当该线程占用的stack大小,没有指定stacksize的大小的话,默认为0, 0代表着会忽略该参数,该参数会被虚拟机中的函数调用,该参数在有些平台有效,在有些平台无效。
三.线程安全
在使用多线程时,大家一定遇到过这样的情况,当我们开多个线程去同时读取然后修改一个数时,会发现,他会读取到相同的数字,这是为什么?
public void test(){
public static int i=0; //1
System.out.println(i); //2
i++; //3
}
假设我们想对这段代码实现多线程,我们期望的结果是让i的输出无重复的输出,但是这段代码在没有保护的情况下很容易出现让两个数字读了两次,这里我们用两个线程去演示这个操作,线程A和线程B,现在线程A走到2位置,输出了i=0的值,然后要执行i++;但是,再刚刚输出完后,处理器又让B线程开始工作,A就停在了2的末尾,线程B走到2位置,输出了i=0,这样就输出了两次i=0造成了线程不安全
出现这种情况,我们就要使用synchronized来让线程“安全”了!
有关synchronized,在这个代码块中程序变成了单线程,尽量范围小的去加锁
在这里介绍有关他的锁,常见的加锁方法:
Object LOCK=new Object();
//1
public void test1(){
public static int i=0;
synchronized(LOCK){
System.out.println(i);
i++;
}
}
//2
public synchronized void test2(){
public static int i=0;
System.out.println(i);
i++;
}
//3
public static synchronized void test3(){
public static int i=0;
System.out.println(i);
i++;
}
这三种方法,第一种是写一个代码块,在这个代码块中执行的代码是单线程执行的,第二种是普通方法加锁,在执行时没有给定锁(例如直接在函数处调用),默认就为this,第三种是对static方法加锁,对static方法给锁时给的是这个类的.class
加锁保证了程序执行的部分安全,但是会出现死锁,这里就是一个死锁的案例:
public class OtherService {
private Object lock=new Object();
public DeadLock deadLock;
public void s1() {
synchronized(lock) {
System.out.println("s1");
}
}
public void s2() {
synchronized(lock) {
System.out.println("s2");
deadLock.m2();
}
}
public void setLo(DeadLock deadLock) {
this.deadLock=deadLock;
}
}
public class DeadLock {
private OtherService otherService=new OtherService();
private Object lock=new Object();
public DeadLock(OtherService otherService) {
this.otherService=otherService;
}
public void m1() {
synchronized(lock){
System.out.println("m1");
otherService.s1();
}
}
public void m2() {
synchronized(lock){
System.out.println("m2");
}
}
}
public class Test {
public static void main(String[] args) {
OtherService o=new OtherService();
DeadLock d=new DeadLock(o);
o.setLo(d);
//线程1
new Thread() {
public void run() {
while(true)
d.m1();
};
}.start();
//线程2
new Thread() {
public void run() {
while(true)
o.s2();
};
}.start();
}
}
在这个案例中,我们一开始执行d.m1()时获得了DeadLock中的锁,d.s2()获得了otherservice中的锁,然后然后再m1方法中又要调用s1()方法,在s2()中又要调用m2()的方法,这就造成线程1持有DeadLock的锁,二线程2持有otherservice的锁,他们互相又要进入对方的加锁代码块,互相又都不肯放弃锁,这就造成了死锁。
解决死锁问题
关于解决死锁,我们可以定义一个显示锁,听名字就是,我们人工的去操作给它锁和释放锁,线程如果拿到锁,执行一段时间,死锁了,那么,这个线程就不动了,我们就人工的释放它的锁,让其他的线程去运行,这样就避免了死锁。
接口,要实现的功能
import java.util.Collection;
public interface Lock {
//异常错误类
class TimeOutException extends Exception{
public TimeOutException(String message) {
super(message);
}
}
//加锁
void lock() throws InterruptedException;
//加锁 时间限制
void lock(long mills) throws InterruptedException,TimeOutException;
//解锁
void unlock();
Collection<Thread> getBlockedThread();
int getBlockedSize();
}
详细的实现
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
public class BooleanLock implements Lock{
//true 表示锁已被获得 fales 表示锁被释放
private boolean initValue;
private Collection<Thread> blockedThreadCollection=new ArrayList<>();
private Thread currentThread;// 用来存储当前线程的
@Override
public synchronized void lock() throws InterruptedException {
while(initValue) {//当锁被别的线程拿着时
blockedThreadCollection.add(Thread.currentThread());
this.wait();
}
blockedThreadCollection.remove(Thread.currentThread());
this.initValue=true;//获得锁
this.currentThread=Thread.currentThread();//记录当前线程
}
@Override
public synchronized void lock(long mills) throws InterruptedException, TimeOutException {
if(mills<0)
lock();
long hasRemainTime=mills;
long endtime=System.currentTimeMillis()+mills;
while(initValue) {
if(hasRemainTime<0)
throw new TimeOutException("超时");
blockedThreadCollection.add(Thread.currentThread());
this.wait();
hasRemainTime=endtime-System.currentTimeMillis();
}
// blockedThreadCollection.remove(Thread.currentThread());
this.initValue=true;
this.currentThread=Thread.currentThread();
}
//释放锁
@Override
public synchronized void unlock() {
if(Thread.currentThread()==currentThread) {
this.initValue=false;
System.out.println("释放锁.....");
this.notifyAll();//唤醒所有
}
}
@Override
public Collection<Thread> getBlockedThread() {
return Collections.unmodifiableCollection(blockedThreadCollection);
}
@Override
public int getBlockedSize() {
return blockedThreadCollection.size();
}
}
测试:
import java.util.ArrayList;
import chapter7.Lock.TimeOutException;
public class LockTest {
public static void main(String[] args) {
final BooleanLock booleanLock=new BooleanLock();
ArrayList<String> list=new ArrayList<>();
list.add("A");list.add("B");list.add("C");list.add("D");
for(String s:list) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//获得锁
booleanLock.lock();
System.out.println(Thread.currentThread()+"获得了锁!");
work();
} catch (Exception e) {
System.out.println("超时");
}finally {
booleanLock.unlock();
}
}
},s).start();;
}
}
private static void work() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"正在工作");
Thread.sleep(400);
}
}
四.常用的API
线程的优先级:getPriority() setPriority()
默认优先级为5,可以企图改变线程的优先顺序(不推荐)
获得Id:在创建线程时,会有一个计数器,每创建一个线程会count++
通过源码可以看出
Jion方法: t1.jion() 等待t1线程执行完后才执行 当前线程(例如在main线程中调用,main就是当前线程) 当两个线程都在启动状态下,对两个线程分别执行join时,两个线程是同步进行
如果同时调用t1.jion() t2.jion() ,当前线程会在t1和t2线程执行完后再执行当前线程,t1和t2时同时进行的,它的等待只是针对当前线程。
如果调用jion(s,ns),那么当先时间就会等待,等待时间一过,当前线程就不再等待。
Thread,currentThread().join();//当前线程等待当前线程执行完(死循环)
案例:当我们要开辟多个线程同时去执行一项任务的多个分支 时,我们需要等待所有线程都执行完了才能一同再执行下一个任务,所以,我们可以让三个线程同时jion进另一个线程。
五.停止线程
interrupt:
interrupt()是对该线程进行一个标记,并不能直接关闭线程
interrupted是用来测试当前线程是否已经结束,但是这是个静态方法
我们在使用线程时不能直接关闭,但可以使用其他方法来关闭多线程
方法一:我们可以通过控制它的循环条件去关闭
public class ThreadCloseGraceful {
private static class Worker extends Thread{
private volatile boolean start=true;
@Override
public void run() {
while(start) {
System.out.println("A");
}
}
public void shutdown() {
this.start=false;
}
}
public static void main(String[] args) throws InterruptedException {
Worker worker=new Worker();
worker.start();
Thread.sleep(10000);
worker.shutdown();
}
}
方法二: 如果一个线程在被interrupt标记后,那么在sleep时就是可以被打断的,所以,我们只需要让线程每次睡眠一会,也就是说每次对线程做一个判断,通过捕获异常的方法关闭线程
public class ThreadCloseGraceful2 {
private static class Worker extends Thread{
@Override
public void run() {
while(true) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
break;
}
}
}
}
public static void main(String[] args) {
Worker worker=new Worker();
worker.start();
worker.interrupt();
}
}
六.线程的优先级
getPriority() setPriority()
优先级高的线程不一定是一定要先执行,而是执行的记几率高一点
七.守护线程
守护线程:setDaemon
将这个线程设置为守护线程,这个函数要在start之前调用。如果在守护线程(t1)上设置守护线程(t2),当第一个守护线程(t1)结束,它的守护线程(t2)也会跟随结束。