文章目录
前言
进程是一个应用程序(或软件)。
线程是一个进程中的执行场景/执行单元,一个进程可以启动多个线程。
一个线程一个栈,方法区内存和堆内存共享。
1、实现线程的三种方式
1.1 第一种方式:
步骤1:编写一个类继承java.lang.Thread类,重写run()方法。
步骤2:创建线程对象
步骤3:启动线程
例子:
public class Main {
public static void main(String[] args) {
//创建线程对象
NewThread nt=new NewThread();
//启动线程
nt.start();
for(int i=0;i<100;i++){
System.out.println("main线程--->"+i);
}
}
}
//第一步:编写一个类继承Thread,并重写run()方法
class NewThread extends Thread{
public void run(){
for(int i=0;i<100;i++){
System.out.println("新线程--->"+i);
}
}
}
1.2 第二种方式:
步骤1:编写一个类实现java.lang.Runnable接口,重写run()方法。
步骤2:利用Thread的Thread(Runnable target)构造方法,创建线程对象
步骤3:启动线程
例子:
public class Main {
public static void main(String[] args) {
//第二步:创建Thread对象,参数传入Callable对象
Thread nt=new Thread(new NewThread());
//第三步:启动线程
nt.start();
for(int i=0;i<100;i++){
System.out.println("main线程--->"+i);
}
}
}
//第一步:创建实现Runnable接口类,重写run()方法
class NewThread implements Runnable{
//重写run()方法
public void run(){
for(int i=0;i<100;i++){
System.out.println("新线程--->"+i);
}
}
}
1.3 第三种方式
步骤1:编写一个类实现java.util.concurrent.Callable接口,重写call()方法.
步骤2:创建java.util.concurrent.FutureTask未来任务类,参数传入实现Callable接口的类。
步骤3:创建线程对象Thread,参数传入FutureTask对象。
步骤4:启动线程
如何获取线程对象,利用FutureTask类的get()方法。
例子:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) throws Exception {
//第二步:创建java.util.concurrent.FutureTask未来任务类,参数传入实现Callable接口的类。
FutureTask task = new FutureTask(new MyCallable());
//第三步:创建线程对象Thread,参数传入FutureTask对象。
Thread t = new Thread(task);
t.setName("t");
//第四步:启动线程
t.start();
//获取t线程返回值
/*
注意:get()方法的执行会导致当前线程阻塞,因为get()方法是为了拿到另一个线程的执行结果。只有get()方法执行结束,main线程才会继续。
*/
Object obj = task.get();
System.out.println("获取t线程的结果" + obj);
System.out.println("main线程受阻塞");
}
}
//第一步:编写一个类实现java.util.concurrent.Callable接口,重写call()方法.
class MyCallable implements Callable{
public Object call() throws Exception {
Thread.sleep(1000 * 5);//模拟运行5秒
return 0;
}
}
2、Thread常用方法:
2.1 构造方法
Thread();
Thread(Runnable target);
2.2 方法
修饰 | 返回值类型 | 方法 | 描述 |
---|---|---|---|
void | start() | 启动线程 | |
static | Thread | currentThread() | 获取当前线程对象的引用,在哪使用,哪就是当前线程 |
void | setName() | 重新设置线程对象名称 | |
String | getName() | 获取线程对象的名字 | |
static | void | sleep(long millis) | 使当前线程休眠(出现在哪个线程,哪个线程休眠) |
void | interrupt() | 终止线程的睡眠,利用异常处理机制 | |
void | stop() | 强行终止线程,容易丢失数据,不建议使用 | |
void | join() | 合并线程,当前线程阻塞,合并的线程直到结束才继续 | |
static | void | yield() | 让位,当前线程回到就绪状态,但有可能还会抢到CPU时间片,继续执行 |
void | setPriority() | 设置线程优先级,最高10,最低1,默认5 | |
int | getPriority() | 获取线程优先级 |
例子:
public class Main {
public static void main(String[] args) {
//创建线程对象
Thread t=new Thread(new NewThread());
//设置线程名字,默认Thread-0
t.setName("NewThread线程");
//启动线程
t.start();
//主线程睡眠5秒
for(int i=5;i>0;i--){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
//获取当前对象的名字,main方法的线程名默认是main
System.out.println(Thread.currentThread().getName()+"线程"+i);
}
//打断t线程睡眠,利用的是异常处理机制
//执行interrupt方法,sleep出现异常,进入catch语句,从而打断睡眠。
t.interrupt();
}
}
//编写类,实现Runnable接口
class NewThread implements Runnable{
//重写run方法
public void run(){
//获取当前对象的名字
System.out.println(Thread.currentThread().getName()+":begin");
try{
//睡眠一天
Thread.sleep(1000*60*60*24);
}catch (InterruptedException e){
//如果不想异常输出,可以注释掉
//e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":over");
}
}
3. Q&A
3.1 stop()方法,强行终止线程容易丢失数据,那怎么终止一个线程?
A:利用标记
public class Main{
public static void main(String[] args){
NewThread nt=new NewThread();
Thread t=new Thread(nt);
t.setName("t线程");
t.start();
//模拟工作5秒
try{
Thread.sleep(1000*5);
}catch (InterruptedException e){
e.printStackTrace();
}
//修改标记,结束线程。
nt.run=false;
}
}
class NewThread implements Runnable{
boolean run=true;
public void run(){
for(int i=0; i<10;i++){
if(run){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+i);
}else{
System.out.println(Thread.currentThread().getName()+"结束");
return;
}
}
}
}
3.2 什么时候数据在多线程并发的情况下会存在安全问题?
三个条件:
- 条件1:多线程并发
- 条件2:数据共享
- 条件3:共享数据有修改的行为
满足以上3个条件之后,就会存在线程安全问题
局部变量+常量,不会有线程安全问题
成员变量(实例变量,静态变量):可能会有线程安全问题
3.3 怎么解决线程安全问题
在以后开发中,我们的项目都是运行在服务器中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。你要知道,你编写的程序需要放到一个多线程的环境下运行的,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。
- 第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
- 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存不共享了。
- 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候只能选择synchronized了,线程同步机制(排队)。
3.4 synchronized的使用
- 第一种:同步代码块:找共享对象
语法结构:
synchronized (共享对象){
//同步代码块
}
- 第二种:在实例方法上使用synchronized,找对象锁,锁的是this
- 第三种:在静态方法上使用synchronized,找类锁。类锁是保证静态变量安全的。
3.5 死锁怎么写?
代码要会写,只有会写了,才会在以后的开发中注意死锁,死锁很难调试。