目录
一、进程与线程的区别
二、实现多线程
三、线程 Thread 中常用的API
四、线程的生命周期
五、线程调度
六、守护线程
七、线程安全问题
Java虚拟机的主线程入口是main方法,用户可以自己创建线程,创建多线程的俩种方法:
* 继承 Thread 类
* 实现 Runnable 接口(推荐使用,因为Java只支持单继承)
第一种方法:继承 Thread 类
Thread 类种俩个重要的方法:
*public void run() //继承 Thread 类时,可重写 run 方法
* public void start() // 用来启动多线程
public class Test01 {
public static void main(String[] args) {
Thread t = new ThreadTest();
//启动线程
t.start();
//这不是启动线程,单纯的调用方法!!
t.run();
}
}
class ThreadTest extends Thread {
@Override
public void run(){
System.out.println("多线程!!");
}
}
start方法的作用是:采用 start 启动线程,在JVM中开辟一个新的栈空间,不是直接调用 run ,start 不是马上执行线程,而是使线程进入就绪 状态,线程的正真执行是由 Java 的线程调度机制完成的
第二种方法:实现 Runnable 接口
Thread是Runnable 的一个实现类,所以Thread中的方法,实现Runnable接口也可以用。
public class Test01 {
public static void main(String[] args) {
Thread t = new Thread(new ThreadTest());
//启动线程
t.start();
}
}
class ThreadTest implements Runnable {
@Override
public void run() {
System.out.println("多线程!!");
}
}
void setName(String s) | 改变线程名字,使之与参数name一样 |
String getName() | 获取该线程的名称 |
void setPriority(int newPriority) | 更改线程的优先级 |
String getPriority() | 获取当前线程优先级 |
void interrupt() | 中断线程的睡眠,并不会中断线程的执行 |
static boolean interrupt() | 测试当前线程是否中断 |
void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程 |
boolean isDaemon() | 测试当前线程是否是守护线程 |
static Thread currentThread() | 返回当前线程对象 |
static void sleep(long millis) | 指定该线程休眠多少毫秒。并没有终止线程 |
static void yield() | 暂停正在执行线程对象,执行其他线程 |
void join() | 合并线程 |
API测试:
public class Test01 {
public static void main(String[] args) {
//线程1
Thread t = new Thread(new ThreadTest());
//线程2
Thread tt = new Thread(new ThreadTest1());
System.out.println( "线程1的初始名字 = " + t.getName() + ",线程2的初始名字 = " + tt.getName());
t.setName("t1");
tt.setName("t2");
System.out.println( "线程1修改后名字 = " + t.getName() + ",线程2修改后名字 = " + tt.getName());
//获取主线程
Thread thread = Thread.currentThread();
System.out.println("主线程的初始名字 = " + thread.getName());
//启动线程
t.start();
tt.start();
}
}
//线程1
class ThreadTest implements Runnable {
@Override
public void run() {
}
}
//线程2
class ThreadTest1 implements Runnable{
@Override
public void run() {
}
}
主线程的初始名字是 main,其他分支线程的初始名字:Thread-0,Thread-1.....
public class Test01 {
public static void main(String[] args) {
//线程1
Thread t = new Thread(new ThreadTest());
//线程2
Thread tt = new Thread(new ThreadTest1());
//启动线程
t.start();
tt.start();
try {
//虽然是用线程1的引用掉用了此方法,但是这并不会让线程1睡眠 3s,sleep写在哪就会让哪个线程休眠
//所以执行到此处,主线程会休眠 3 s
t.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是主线程");
}
}
//线程1
class ThreadTest implements Runnable {
@Override
public void run() {
System.out.println("我是线程1");
}
}
//线程2
class ThreadTest1 implements Runnable{
@Override
public void run() {
try {
//sleep写在这里会让 线程 2 休眠5s
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是线程2");
}
}
public class Test01 {
public static void main(String[] args) {
//线程1
Thread t = new Thread(new ThreadTest());
//线程2
Thread tt = new Thread(new ThreadTest1());
//启动线程
t.start();
tt.start();
// interrupt 会中断线程2的休眠。但不会终止线程的执行,所以线程2的for语句还会执行。
//interrupt依靠的是 sleep方法 的异常处理机制。
tt.interrupt();
//查询中断状态:true 为中断成功,false 为中断失败
boolean interrupted = tt.isInterrupted();
System.out.println(interrupted);
}
}
//线程1
class ThreadTest implements Runnable {
@Override
public void run() {
}
}
//线程2
class ThreadTest1 implements Runnable{
@Override
public void run() {
try {
Thread.sleep(50000000);
} catch (InterruptedException e) {
System.err.println("已经终止睡眠");
}
for (int i = 0; i < 10; i++) {
System.out.print(i + "\t");
}
}
}
线程的生命周期分为以下五个:
使线程进入阻塞状态的方法:
sleep、wait、join、yield
1、常见的调度模型有哪些?
抢占式调度模型
哪个线程的优先级较高,抢夺CPU时间片的概率就会高,Java采用的就是这种模型
均分式调度模型
平均分配CPU时间片,每个时间占用的时间长度一样
2、线程调度的方法
更改线程的优先级
注意:优先级高低,只是将线程抢夺CPU时间片的概率高一些,并不一定会优先执行
Thread.MIN_PRIORITY:最小优先级1 Thread.MAX_PRIORITY:最大优先级10 Thread.NORM_PRIORITY:默认优先级5
yield()方法 暂停当前线程的运行,让给其他线程执行。没有终止线程,只是将当前线程从运行状态转换到就绪状态
join() 方法,合并俩个线程
1、Java中的守护线程分为俩类:
一类是守护线程,一类是用户线程
比如:垃圾回收器就是守护线程,主线程是一个用户线程
2、守护线程的特点
一般是一个死循环,当所有用户线程结束,守护线程自动结束
public class Test2 {
public static void main(String[] args) {
Thread t = new Thread(new ThreadTest2());
t.setName("守护线程");
//将线程t设置为守护线程
t.setDaemon(true);
t.start();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程 i-->" + i + "\t");
}
}
}
class ThreadTest2 implements Runnable {
@Override
public void run() {
int i = 0;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护线程 i-->" + (++i));
}
}
}
虽然守护线程是个死循环,但是当用户线程结束时,守护线程会自动结束
★★★★★ 1、什么时候数据在多线程开发的时候会遇到安全问题?
多线程并发
共享数据
修改数据
共同存在以上三种行为有可能会引发多线程安全问题
2、同步编程模型和异步编程模型的概念
同步编程模型:假设俩个线程 t1和t2,线程 t1 执行的时候必须等待 t2 执行完才能执行,也就是线程排队机制
异步编程模型:t1,t2 自己执行自己的,谁也不管谁,也就是多线程并发
3、怎么解决线程安全问题
线程排队执行,叫做线程同步机制
4、线程排队机制的语法
synchronized(){ //线程同步代码块 } //synchronized后面这个小括号里面传的这个 “数据” 非常重要, //这个数据必须是多线程共享的数据,才能达到多线程排队。
5、 实现synchronized 的三种方法
第一种方法:
synchronized(){}
第二种方法:
出现在实例方法上,这个同步的是整个方法,效率第,共享的对象只能是 this
public static void synchronized(){}
第三种方法:
出现在类上,无论创建多少个对象,类锁只有一个
6、通过一个例子体现线程安全的重要性以及实现线程同步的用法
设计一个程序,实现简单的银行转账功能。
目的:假设银行账户余额为10000,俩个人同时去银行对同一个账户进行取钱操作,假设每个人取5000,不使用线程同步时,账户余额是否有可能还剩下 5000 ?
public class AccountTest {
public static void main(String[] args) {
//初始化账户
Account account = new Account(123, 10000);
//俩个线程代表俩个用户
Thread t1 = new Thread(new AccountThread(account));
Thread t2 = new Thread(new AccountThread(account));
//用户开始取钱
t1.start();
t2.start();
}
}
//账户类
class Account {
//账号
int no;
//余额
double balance;
//构造器
public Account(int no, double balance) {
this.no = no;
this.balance = balance;
}
//取钱的方法
public void getMoney(double money) {
//保证出现实验效果,每个线程休眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//剩余余额
this.balance = balance - money;
}
}
//一个用户就是一个线程
class AccountThread implements Runnable {
//模拟线程安全问题,保证多个线程都共享一个对象
private Account account;
public AccountThread(Account account) {
this.account = account;
}
@Override
public void run() {
double money = 5000;
account.getMoney(money);
System.out.println("账号 = " + account.no + ", 取走 = " + money + ", 余额 = " + account.balance);
}
}
实验结果:我们可以看到,当俩个用户取钱时,账户余额本应该剩余 0,但是却剩余 5000,这个时候就出现线程安全问题
解决办法就是使用线程排队机制:每个用户进行取钱操作时,一个执行,另一个等待。
此时,取钱操作就不会出现安全问题了。共享的对象就是 银行账户。也就是 this。
线程排队的原理:
在java中,每个对象都有一把 "锁",100个对象有100把 "锁"。线程 t1 和 t2 共享一个对象,所以只会有一把 "锁",假设 t1 先执行到 synchronized 处,会霸占这把 "锁" ,拿着这把 "锁" 会先执行取钱操作,而当 t2 执行到此处时,由于这把 "锁" 被 t1 霸占,所以 t2 会等着 t1 先执行完取钱操作, t2 才会执行,这样就不会出问题
6、关于Java中的变量,哪种变量永远不会有线程安全问题,哪种变量可能存在安全问题?
Java中的变量:
实例变量【全局变量】:保存在堆内存中
局部变量:保存在栈内存中
静态变量:保存在方法区中
只有局部变量永远不会有线程安全问题,因为在多线程访问变量时,每个线程都有一个栈内存。而实例变量,静态变量可能会存在线程安全问题