多线程技术
1、什么是进程,什么是线程
进程是一个应用程序(一个软件)
线程是一个进程中的执行场景/执行单元 一个进程可以同时启动多个线程
2、在java中,多线程共享堆内存和方法区内存的内容,但是栈内存独立,一个线程一个栈
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发,可以提高效率
3、在java中,实现线程有三种方式
(1)编写一个类,直接继承java.lang.Thread
/*
实现线程的第一种方式
编写一个类,直接继承java.lang.Thread,重写run()方法
怎么创建线程对性,怎么启动线程?
*/
public class ThreadTest1 {
public static void main(String[] args) {
//这里是main方法,这里的代码属于主线程
//新建一个分支线程对象
MyThread myThread = new MyThread();
//启动线程
myThread.start();
/*start方法的作用启动一个分支线程,在jvm中开辟一个新的栈空间,这段代码完成之后,瞬间就结束了
这段代码的任务只是为了开辟一个新的栈空间,只要新的栈空间开辟出来,start方法就结束了
启动成功的线程会自动调用run方法,并且run方法在分支栈的底部(压栈)(它和mian是平级的)。
如果不写启动程序,直接写 myThread run()方法,并没有起到多线程的作用,还是在一个线程中运行的
*/
//接下来的代码还是运行在主线程中
}
}
class MyThread extends Thread{
@Override
public void run() {
// 在这里编写程序,这段程序运行在分支栈中
//这里的run可以简单的想象为主程序里的main
System.out.println("这段程序运行在分支栈中");
}
}
(2)编写一个类实现java.lang.Runnable接口,比较常用(面向接口编程)
public class ThreadTest2 {
public static void main(String[] args) {
//创建一个可运行的对象
MyRunnable r = new MyRunnable();
//将可运行的对象封装成一个线程对象
Thread t = new Thread(r);
//启动线程
t.start();
}
}
//这并不是一个线程类,是一个可运行的类,它还不是一个线程
class MyRunnable implements Runnable{
@Override
public void run() {
}
}
(3)实现Collable接口(JDK 8新特性)
这种方式实现的线程可以获取线程的返回值。
public class ThreadTest10 {
public static void main(String[] args) {
//创建一个“未来任务类对象”
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {//call方法相当于run方法,只不过有返回值
Thread.sleep(10000);
return new Object();
}
});
//创建线程对象
Thread t = new Thread(task);
//启动线程
t.start();
//在主线程中 如何获取t线程的返回值结果?
Object o = task.get();
//get方法的执行,会导致当前线程阻塞
}
}
4.、线程的三个常用方法
如何获取当前线程对象
thread currentThread = thread.currentThread();这是一个静态方法,
获取线程对象的名字,修改
m.getName()
m.setName("mmm");
5、线程的sleep方法
static void sleep(long millis)这是一个静态的方法,单位是毫秒
Thread.sleep(1000) 作用:让当前线程计入休眠状态,
也可与以用于间隔特定的时间再执行其它的代码
注意它是一个静态的方法,就算你用引用去调用它,那他执行时也是执行Thread.sleep(1000),最后也只会在当前线程中起作用。
如何唤醒一个正在睡眠的线程
public static void main(String[] args) {
Runnable target;
Thread m = new Thread(new MyRunnerable2());
m.setName("mmm");
m.start();
//希望m线程5秒钟后醒来
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打断线程的睡眠。这里利用的异常的处理,程序显示异常,然后代码执行完毕
m.interrupt();
}
}
class MyRunnerable2 implements Runnable{
@Override
public void run() {
System.out.println("睡眠开始");
try {//睡的时间长一点
Thread.sleep(1000*60*60);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("睡眠结束");
}
6、在java中如何强行终止一个线程?
public static void main(String[] args) throws InterruptedException {
MyRunnerable4 m = new MyRunnerable4();
Thread t = new Thread(m);
t.setName("ttt");
t.start();
Thread.sleep(1000*5);
m.run = false;
//你想什么时候终止t的执行,那你就把标记run改为false就结束了
//t.stop();//已过时
//缺点:容易丢失数据
}
class MyRunnerable4 implements Runnable{
//打印一个boolean标记
Boolean run = true;
@Override
public void run() {
for(int i =0 ;i <10 ;i++){
if(run) {
System.out.println(Thread.currentThread().getName() + i);
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{//终止当前线程
return;
}
}
}
7、关于多线程并发环境下,数据的安全性问题(重要)
1、什么时候数据在多线程并发的环境下会存在安全问题
三个条件: 1、多线程并发
2、有共享数据
3、共享数控有修改行为
2、如何解决
线程排队执行,不能并发,这种机制被称为线程同步机制,线程同步就是线程排队了,线程排队了就会牺牲一部分效率。安全第一,效率第二
涉及到两个专业术语
异步编程模型:线程t1,t2各自执行各自的,谁也不需要等谁,这种编程模型就是多线程并发
同步编程模型:就是t1和t2各自执行的时候必须相互等待对方执行完毕,这就是排队
锁的概念
/*在java语言中,任何对象都有一把锁(这其实是一个标记)
以下代码执行原理,假设t1和t2线程并发,若t1先执行了遇到synchronized,这个时候会自动找“后面共享对象”的对象锁
找到之后,并占有这把锁,然后执行同步代码块中的程序。直到同步代码块代码结束,这把锁才汇释放。直到t1释放了,t2才会执行
这就达到了线程排队执行
这里需要注意:()中的共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享
*/
线程同步机制的语法:
synchronized的语法:
第一种:同步代码块 synchronized(){
同步代码块
}
第二种:在实例方法上使用synchronized
表示同步对象一定是this,并且同步代码块是整个方法体
第三种:在静态方法上使用synchronized
表示找类锁,类锁永远只有一把,就算创建100个对象,也只有一把
对象锁是一个对象一把,类锁是100个对象也可能只是1把锁
3、死锁的概念(面试可能会叫你写)
public class DeadLocked {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new MyThread1(o1,o2);
Thread t2= new MyThread3(o1,o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread {
Object o1;
Object o2;
public MyThread1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run(){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread3 extends Thread{
Object o1;
Object o2;
public MyThread3(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run(){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
4、以后开发中如何解决线程同步问题
1、尽量使用局部变量代替 实例变量 和 静态变量
2、如果必须是实例变量,可以考虑创建多个对象,这样实例变量的内存就不共享了
3、以上两种都不行的话,选择 synchronized
5、守护线程
特点:一般守护线程是一个死循环,所有的用户线程结束,守护线程就自动结束
用在什么地方呢?
6、定时器
作用:间隔特定的时间执行特定的程序
7、关于Object类中的 wait 方法和 notify 方法(生产者和消费者模式)
-
wait 和 notify 方法不是线程对象的方法,java中的任何一个对象都有,因为是Object自带的
-
他们不是通过线程对象去调用的
-
wait()方法作用:Object o = new Object();
o.wait(); 表示正在o对象上活动的线程进入无期限等待状态,直到被唤醒为止
o.notify();可以让正在o对象上等待的线程被唤醒