文章目录
多线程是实现并发机制的一种手段,多线程是为了同步完成多项任务,不是为了提高程序运行效率。
1 多线程的开启
1.1 Thread类创建多线程
class MyThread extends Thread{
public void run(){ //重写run
int i = 1;
while (i<=3) {
System.out.println(this.getName() + ":" + i);
i++;
}
}
}
public class Demo01 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
/*运行结果:
Thread-1:1
Thread-0:1
Thread-1:2
Thread-0:2
Thread-1:3
Thread-0:3
进程已结束,退出代码为 0
*/
Thread提供了以下函数:
1.String getName():返回调用该方法的线程名字;
2.void start():使该线程开始执行;
3.void setName(String name):为线程设置名字,在默认情况下,主线程的名字为 main,用户启动的多个线程的名字依次为 Thread-0、Thread-1、Thread-2、…、Thread-n 等;
4.String getName():返回调用该方法的线程名字;
5.boolean isAlive():测试线程是否处于活动状态;
1.2 Runnable 接口创建多线程
采用 Runnable 接口的方式创建的多个线程可以共享同一个 target 对象的实例变量(Java只支持单继承)
ublic static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread th1 =new Thread(r,"线程1");// 将 target 作为运行目标来创建创建 Thread 类的对象
Thread th2 =new Thread(r,"线程2");
th1.start();;// 调用线程对象的 start() 方法来启动该线程
th2.start();
}
}
class MyRunnable implements Runnable{
public void run(){
int i = 1;
while (i<=3) {
System.out.println(Thread.currentThread().getName() + ":" + i);
i++;
}
}
}
/*运行结果:
线程2:1
线程1:1
线程2:2
线程1:2
线程2:3
线程1:3
进程已结束,退出代码为 0
*/
1.3 使用 Callable 和 FutureTask 创建线程
Runnable方法没有返回值,也不能抛出异常。Callable 接口提供了一个 call() 方法(可以有返回值,可以声明抛出异常)可以作为线程执行体。
class MyCallable implements Callable<Integer> {
int i = 1;
int sum = 0;
public Integer call(){ //重写call方法
for(i=1 ; i<4 ; i++ ) {
System.out.println(sum += i);
}
return sum;
}
}
public class Demo03 {
public static void main(String[] args) {
//创建MyCallable对象
MyCallable mc = new MyCallable();
//使用FutureTask来包装MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(mc);
Thread thread = new Thread(ft);
thread.start(); //线程就绪
//用get方法读取ft的返回值
try {
int sum = ft.get();
System.out.println("最终结果为: " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(e);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
1.4 创建线程的三种方式对比
1.继承 Thread 类
线程类已经继承了 Thread 类,不能再继承其它父类
2.实现Runnable接口创建多线程
避免了单继承的局限
3.实现 Runnable、Callable 接口的方式创建多线程
避免了单继承的局限,有返回值,可抛出异常。
弊端:编程较为复杂,如果需要访问当前线程,则必须使用 Thread. currentThread() 方法。
2 线程的生命周期
新建、可运行(就绪、运行)、阻塞、等待、计时等待、终止/死亡
3.0 线程的调度
如果一台电脑只有一个cpu,每个线程只有得到cpu的使用权的时候才能执行指令。java虚拟机按照一定的规律对其进行调度。
3.1 线程的优先级
java中可以通过设置优先级来改变线程的调用顺序。
setPriority(Thread.MIN_PRIORITY);//设置低优先级
setPriority(Thread.MAX_PRIORITY);//设置高优先级
//setPriority()为Thread类的方法
3.2 线程休眠
Thread类存在静态方法sleep()方法使其休眠(进入阻塞状态)
static void sleep(long millis) //指定毫秒数
static void sleep(long millis, int nanos) //指定毫秒数和微秒数
3.3 线程让步
yield()方法也可以使线程暂停,但是不会使其阻塞
3.4 线程插队
Thread中提供join()方法,当一个线程中调用其他线程的 join() 方法时,此线程将被阻塞,直到被调用的线程运行完成为止
3.5 后台线程
又叫守护线程,垃圾回收机制就是一个后台线程,如果所有的前台线程都死亡了,虚拟机便会退出,后台线程不会单独运行。
Thread中steDeamon(boolean on )可以创建后台线程。
4.多线程同步
4.1线程安全
当多个线程同时运行同一代码使,可能出现运行错误问题,这个问题可能会在多次运行时暴露出来。经典例题为——窗口买票问题。
public class Demo04 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
//定义票的总数
private int n = 5;
@Override
public void run() {
for(int i = 1 ; i <=100 ; i++){
if (n>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在卖第" + n + "张票,剩余" + --n + "张票");
}
}
}
}
/*运行结果:
正在卖第5张票,剩余4张票
正在卖第4张票,剩余3张票
正在卖第5张票,剩余4张票
正在卖第3张票,剩余2张票
正在卖第3张票,剩余0张票
正在卖第3张票,剩余1张票
进程已结束,退出代码为 0
*/
可以看到,第五和第三张票都被卖了多次,实际多次运行结果各有不同,但是都可以说明,线程的运行不同步,这就是线程安全问题。
4.2 同步方法
方法一:同步监视器可以解决上述问题
public class Demo04 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
//定义票的总数
private int n = 5;
@Override
public void run() {
for(int i = 1 ; i <=100 ; i++){
synchronized (this){ //同步监视器
if (n>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在卖第" + n + "张票,剩余" + --n + "张票");
}
}
}
}
}
/*结果:
正在卖第5张票,剩余4张票
正在卖第4张票,剩余3张票
正在卖第3张票,剩余2张票
正在卖第2张票,剩余1张票
正在卖第1张票,剩余0张票
进程已结束,退出代码为 0
*/
方法二:同步方法
public class Demo04 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
//定义票的总数
private int n = 5;
@Override
public synchronized void run() { //此处使用了同步方法synchronized关键字
for(int i = 1 ; i <=100 ; i++){
if (n>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在卖第" + n + "张票,剩余" + --n + "张票");
}
}
}
}
原理:当线程执行同步代码块时,会先检查同步监视器的标志位,默认为1,若有线程在执行代码块,会将标志改为0,下一个编程若看到标志位为0,便会进入堵塞状态,直到标志位为1,继续执行。
4.3 死锁问题
当不同的线程分别占用对方需要的资源不放弃,便会带来是死锁问题。在编写代码的时候应尽量避免,Thread类的suspend()方法也容易导致死锁问题。
5 多线程通讯
不同的线程在执行问题时,如果任务间有联系,则需要多线程通讯。
Objet类的通讯方法:
void wait() 、void notify() 、void notifyAll()。
以上三种方法只有在synchronized方法或者synchrinized代码块中才能使用。
6 线程组和未处理的异常
ThreadGroup可以创建线程组,他可以对线程进行分类管理,Java允许程序直接对线程进行控制。线程在与运行时,不能改变其线程组。
Thread(。。。)
Thread Group(。。。)
还有更多方法,方法的使用方法此处不做过多介绍
public class Demo05 {
public static void main(String[] args) {
ThreadGroup tg1 = Thread.currentThread().getThreadGroup(); //获得当前线程组(默认为主线程组)
System.out.println("主线程的名字为:" + tg1.getName());
System.out.println("主线程是否为后台线程:" + tg1.isDaemon());
SubThread st = new SubThread("这是主线程组的线程");
st.start();
ThreadGroup tg2 = new ThreadGroup("这是一个新线程组"); //新建一个线程组
tg2.setDaemon(true); //设置线程组的后台程序状态
System.out.println("新线程组的是否为后台线程组:" + tg2.isDaemon());
SubThread st2 = new SubThread(tg2 , "这是新线程组的线程");
}
}
class SubThread extends Thread{
public SubThread(String name){
super(name);
}
public SubThread(ThreadGroup group , String name){ //ThreadGroup代表一个线程组名
super(group , name);
}
public void run(){
for (int i = 0; i<=5 ;i++){
System.out.println(this.getName() + "第" + i + "次运行");
}
}
}/*运行结果:
主线程的名字为:main
主线程是否为后台线程:false
新线程组的是否为后台线程组:true
这是主线程组的线程第0次运行
这是主线程组的线程第1次运行
这是主线程组的线程第2次运行
这是主线程组的线程第3次运行
这是主线程组的线程第4次运行
这是主线程组的线程第5次运行
进程已结束,退出代码为 0
*/
此处测试了很多种相关线程组的方法,仅供参考。
线程未知异常:当JVM结束该线程之前没有查到对应的Thread.UncaughtExceptionHandler时,需要一个类来检测其异常
public class Demo06 {
public static void main(String[] args) {
Thread.currentThread().setUncaughtExceptionHandler(new MyHander());
int i = 1/0;
System.out.println("系统走到这了");
}
}
class MyHander implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t,Throwable e){
System.out.println(t + "线程出现了异常" + e);
}
}
/*运行结果:
Thread[main,5,main]线程出现了异常java.lang.ArithmeticException: / by zero
进程已结束,退出代码为 1
*/