一、线程、进程与多线程
(一)进程
应用程序的执行实列
有独立的内存空间和系统资源
CPU在某个时间点只能执行一个进程
(二)线程
线程是CPU和分派的基本单位
执行运算的最小单位,可以完成一个独立的顺序控制流程
一个进程中存在多个线程
(三)多线程
若在一个进程中同时运行多个进程,用来完成不同的工作,则称之为“多线程”
多个线程交替占用CPU资源,而非真正的并行执行
(四)多线程的好处
充分利用CPU的资源,提升代码的性能
简化编程模型,更好的软件设计和架构
为用户带来良好的体验
二、线程的创建
(一)主线程
main()方法为主线程的入口,必须在最后完成执行
public static void main(String[] args) { //获取当前的线程对象 Thread thread = Thread.currentThread(); //获取当前线程对象的而名称 String name = thread.getName(); System.out.println(name); //为当前线程重新设置名称 thread.setName("brx"); name = thread.getName(); System.out.println(name); }
(二)继承Thread类创建线程
java.lang.Thread类
创建要点:
定义MyThread类继承Thread类
重写run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
public class MyThread extends Thread{//继承Thread
@Override
public void run() {//重写run方法
for (int i = 1; i <= 100; i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public static void main(String[] args) {//main()方法为主线程的入口
//创建线程对象
MyThread mythread = new MyThread();
MyThread my1 = new MyThread();
//启动线程
mythread.start();
my1.start();
}
注意:启动线程时不可以调用run()方法
run()方法:只有主线程一天执行路径
start()方法:多条执行路径,主线程和子线程并行交通执行
(三)使用Runnable接口创建线程
实现java.lang.Runnable接口
使用方法:
定义MyRunnable类实现Runnable接口
实现run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
public class MyRunnable implements Runnable{//Runnable接口
@Override
public void run() {//run()方法
for (int i = 0;i <= 100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public static void main(String[] args) {
//创建线程对象
Runnable runnable = new MyRunnable();
//方式一:Thread(Runnable target)
Thread thread1 = new Thread(runnable);
//方式二:Thread(Runnable target,String name),对当前线程重命名
Thread thread2 = new Thread(runnable,"mythread");
//启动线程
thread1.start();
thread2.start();
}
(四)实现Callable<>创建线程
实现Callable<数据类型>,
重写call()方法,编写线程执行体
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0;i <= 10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
return null;
}
}
ublic class MyCallableTest {
public static void main(String[] args) {
MyCallable mc = new MyCallable();
FutureTask ft = new FutureTask<String>(mc);
Thread t = new Thread(ft);
t.start();
}
}
(五)继承Thread类与实现Runnable接口区别
继承Thread类
编写简单,可直接操作线程
适用于单继承
多个线程分别完成自己的任务
实现Runnable接口
避免单继承局限性
便于共享资源
多个线程共同完成一个任务
三、线程状态
创建状态:new Thread时为创建状态
就绪状态:start()方法时为就绪状态
运行状态:当调用run()方法时为运行状态
阻塞状态:当调用时sleap(),yield()、join()、wait()、notify时为阻塞状态
死亡状态:线程执行完毕,终止线程
四、线程的调度
(一)常用方法
方 法 | 说 明 |
---|---|
void setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程 |
boolean isAlive() | 测试线程是否处于活动状态 |
setPriority():
线程优先级由1~10表示,1最低,默认优先级为5
优先级高的线程获得CPU资源的概率较大
public class MyThread extends Thread{//继承Thread
@Override
public void run() {//重写run方法
for (int i = 1; i <= 100; i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.setPriority(1);
my2.setPriority(10);
my1.start();
my2.start();
}
sleep():
让线程暂时睡眠指定时长,线程进入阻塞状态
睡眠时间过后线程会再进入可运行状态
public static void show(long s){
for (int i =1;i <= s;i++){
System.out.println(i+"秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
System.out.println("--------------开始休眠---------------");
SleepTest.show(5);
System.out.println("--------------结束休眠---------------");
}
join():
强制执行当前线程,join写在哪个线程,就阻塞谁
public void run() {
for (int i =0;i <= 20;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) {
Runnable runnable = new joinTest();
Thread t1 = new Thread(runnable);
t1.start();
for (int i = 0;i <=30;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
if (i == 5){
try {
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
yield():
暂停当前正在执行的线程对象,并执行其他线程,线程礼让
只是提供了一种可能,但不能保证一定会实现礼让
public void run() {
for (int i = 1;i <= 5; i++){
System.out.println(Thread.currentThread().getName()+":"+i);
if (i == 3){
System.out.print("礼让");
Thread.yield();
}
}
}
public static void main(String[] args) {
Runnable runnable = new yieldTest();
Thread t1 =new Thread(runnable);
Thread t2 =new Thread(runnable);
t1.start();
t2.start();
}
(二)阻塞对比:
sleep():让线程暂时睡眠指定时长,线程进入阻塞状态,睡眠时间过后线程会再进入可运行状态
join():礼让,放手,当前线程处于就绪状态
yield():阻塞当前线程,直到其他线程执行完毕,当前线程才进入就绪状态
wait() :让某个线程先暂停下来等一等
notify(): 就是把该线程唤醒,让它能够继续执行
五、多线程共享数据
多个线程操作同一共享资源时,将引发数据不安全问题
synchronized就是为当前的线程声明一把锁
(一)同步方法
1、访问修饰符 synchronized 返回类型 方法名(参数列表){……}
2、synchronized 访问修饰符 返回类型 方法名(参数列表){……}
private int tickets = 20;
private int coun = 0;
boolean bool = false;
@Override
public void run() {
while (!bool){
sale();
}
}
public synchronized void sale(){
if (tickets <= 0){
bool = true;
return;
}
tickets --;
coun ++;
System.out.printf(Thread.currentThread().getName()+":在抢到了第%d张票,剩余%d张票\n",coun,tickets);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
Runnable runnable = new MySynchronized1();
Thread t1 = new Thread(runnable,"智行");
Thread t2 = new Thread(runnable,"携程");
Thread t3 = new Thread(runnable,"花小猪");
t1.start();
t2.start();
t3.start();
}
(二)同步代码块
@Override
public void run() {
while (true){
synchronized (this){
if (tickets <= 0){
return;
}
count ++;
tickets --;
System.out.printf(Thread.currentThread().getName()+":在抢到了第%d张票,剩余%d张票\n",count,tickets);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
(三)总结
1、同一时刻只能有一个线程进入synchronized(this)同步代码块
2、当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
3、当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
六、线程安全
(一)类型对比
1、Hashtable && HashMap
Hashtable
继承关系实现了Map接口,Hashtable继承Dictionary
类线程安全,效率较低
键和值都不允许为null
HashMap
继承关系实现了Map接口,继承AbstractMap类
非线程安全,效率较高
键和值都允许为null
2、StringBuffer && StringBuilder
前者线程安全,后者非线程安全