一、相关概念
1、进程
正在运行的程序
2、线程
线程是进程中一个负责程序执行的控制单元(执行路径)
- 一个进程可以有多个线程,即为多线程;
- 一个进程中至少要有一个线程;
3、多线程的意义
(1)好处
解决了多部分同时运行的问题;
(2)弊端
线程太多,回收效率降低;
(3)多线程的实质
多线程其实是由cpu在做着快速的切换应用程序完成的,这个切换是随机的。由于切换速度太快,让人以为是同时执行;
(4)注意
- 在多线程程序中,只要有一条线程没完成,该程序都还没有完成;即使main线程完成了;
- 各个线程独立运行,互不影响;即使某个线程出现异常,其他线程也不受影响;
4、线程的四种状态
二、创建多线程的俩种方法
1、继承Thread类
(1)步骤
- 定义一个类继承Thread类;
- 并覆盖其run方法,在run方法中写关于你要执行的程序;
- 创建Thread的子类对象创建线程;
- 调用start方法开启线程,并调用run方法执行线程;
(2)举例
public class Doem extends Thread{
String name;
Doem(String name){
this.name = name;
}
public void run(){
for(int i=0;i<10;i++){
System.out.println(name);
}
}
}
public class Test {
public static void main(String[] args) {
Doem a1 = new Doem("xiaoqiang");
Doem a2 = new Doem("旺财");
a1.start();
a2.start();
}
}
(3)Thread类介绍
- String getName();//获取线程名称
- public static Thread currentThread();//获取当前运行的线程
2、实现Runnable接口
(1)步骤
- 定义类实现Runnable接口;
- 覆盖接口中的run方法,将线程的任务代码封装到run方法中;
- 通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造参数进行传递;
- 调用线程对象的start方法开启线程;
(2)举例
public class Doem implements Runnable{
String name;
Doem(String name){
this.name = name;
}
public void run(){
for(int i=0;i<10;i++){
System.out.println(name+" "+Thread.currentThread().getName());
}
}
}
public class Test {
public static void main(String[] args) {
Doem a1 = new Doem("xiaoqiang");
Doem a2 = new Doem("旺财");
Thread test1 = new Thread(a1);
Thread test2 = new Thread(a2);
test1.start();
test2.start();
}
///////////结果///////////
旺财 Thread-1
xiaoqiang Thread-0
xiaoqiang Thread-0
xiaoqiang Thread-0
xiaoqiang Thread-0
xiaoqiang Thread-0
旺财 Thread-1
xiaoqiang Thread-0
xiaoqiang Thread-0
xiaoqiang Thread-0
xiaoqiang Thread-0
xiaoqiang Thread-0
旺财 Thread-1
旺财 Thread-1
旺财 Thread-1
旺财 Thread-1
旺财 Thread-1
旺财 Thread-1
旺财 Thread-1
旺财 Thread-1
(3)实现Runnable接口方式的好处
- 将线程的任务(run方法)从线程的子类中分离出来,将其分装成对象;
- 在Java中,一个类只能继承一个类,但可以实现多个接口。如果一个类已经继承一个类,那么就可以实现Runable接口来实现多线程的功能;
由于第2点好处,所以实现多线程功能常用实现Runable接口的方法。
3、俩种方式的比较
三、多线程中俩个主要问题
1、前言
在学习多线程开始,也是我们大多数刚学多线程的例子,都会遇到俩个问题:“共享资源”、“代码安全隐患”。先大体上说明这俩个问题和其解决办法:
2、示例
(1)售卖火车票
在四个售票窗口同时销售一个火车票
(2)错误代码示例
public class Ticket extends Thread{
//火车票的总票数
private int num = 100;
//卖票
public void sale(){
while(true){
if(num>0){
System.out.println(Thread.currentThread().getName()+"---"+num--);
}
}
}
//多线程的任务
public void run(){
sale();
}
}
/////////////////////////////////////////////////////////
public class Doem {
public static void main(String[] args) {
//建立四个多线程
Ticket doem1 = new Ticket();
Ticket doem2 = new Ticket();
Ticket doem3 = new Ticket();
Ticket doem4 = new Ticket();
//四个线程开始售票
doem1.start();
doem2.start();
doem3.start();
doem4.start();
}
}
分析:这里就犯了“共享资源”和“代码安全隐患”问题。
- private int num = 100,是共享的,像上述代码那样写,属于每个线程都有100张票,这自然是不可以的;
- 第8、9行代码,没有同步。可能会造成出现售卖0、-1、-2张票的情况;
(3)解决“共享资源”问题的方法
- 当继承Thread类实现多线程时
在继承Thread类上,找出要共享的私有属性,在其加上“static”关键字;
public class Ticket extends Thread{
//火车票的总票数
private static int num = 100;
//卖票
public void sale(){
while(true){
if(num>0){
System.out.println(Thread.currentThread().getName()+"---"+num--);
}
}
}
//多线程的任务
public void run(){
sale();
}
}
- 实现Runnable接口实现多线程时
在实现Runnable接口的类上,找出要共享的私有属性,在其加上“static”关键字;
/*建立任务对象*/
public class Ticket implements Runnable{
//火车票的总票数
private static int num = 100;
//卖票
public void sale(){
while(true){
if(num>0){
System.out.println(Thread.currentThread().getName()+"---"+num--);
}
}
}
//多线程的任务
public void run(){
sale();
}
}
虽然这俩种方式都是加“static”关键字来解决“共享资源”问题。但是第二种实现Runnable接口这种方式比第一种更好,实现Runnable接口相当于建立一个“任务对象”,更符合面向对象的思想。
(4)解决“代码安全隐患”问题
- 同步代码块介绍
1、格式
synchronized (任意对象) {
需要同步的代码块;
}
synchronized的“任意对象”称之为锁。当一个线程进入同步代码块时,它会将“锁”"锁上",防止其他线程再次进入;当该线程离开同步代码块时,它就会将“锁”“打开”。这样,其他线程就有可能进入同步代码块了。
2、同步的优、缺点
同步的好处:解决了线程的安全问题;
同步的弊端:程序整体代码效率低;
3、注意
同一个任务时、在同步时,是多个线程使用同一个锁。
- 同步代码块解决问题
public class Ticket implements Runnable{
//火车票的总票数
private static int num = 100;
Object obj = new Object();
//卖票
public void run(){
while(true){
synchronized (obj) {
if(num>0){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+num--);
}
}
}
}
}
//////////////////////////////////////////////////////////////////////
public class Doem {
public static void main(String[] args) {
//建立任务对象
Ticket test = new Ticket();
//创建四个线程,共同完成一个任务对象
Thread doem1 = new Thread(test);
Thread doem2 = new Thread(test);
Thread doem3 = new Thread(test);
Thread doem4 = new Thread(test);
//开启四个线程
doem1.start();
doem2.start();
doem3.start();
doem4.start();
}
}
-
同步函数介绍
1、格式
在需要同步函数加“synchronized”关键词
2、同步函数的锁
是this,谁使用,锁就是当前对象 -
使用同步函数解决问题
1、需求介绍
一个银行,俩个用户分别向该银行存钱
2、代码实现
public class Bank {
//该银行的总存钱数
private int sum = 0;
//向银行存钱
public synchronized void add(int num){
sum = sum + num;
System.out.println("sum="+sum);
}
}
public class Customer implements Runnable{
private Bank bank = new Bank();
//顾客的存钱方法
public void run(){
for(int x=0;x<3;x++){
bank.add(100);
}
}
}
public class Doem {
public static void main(String[] args) {
//多线程任务:存钱
Customer customer1 = new Customer();
//
Thread doem1 = new Thread(customer1);
Thread doem2 = new Thread(customer1);
//
doem1.start();
doem2.start();
}
}
////////////////////////////////////////////
sum=100
sum=200
sum=300
sum=400
sum=500
sum=600
- 关于锁的说明
在同步函数上自然没必要说明,它指定了,我们也无需改变;但是,在同步代码块呢?
在使用同步代码时,我们一定要看清楚,使用该“同步代码”的线程是哪几个,这几个线程所对应的唯一一个对象,又是谁?一定要记住“多个线程,对应一个相同的锁”。
这里推荐在锁上写,实现Runnable接口的类的class对象。例如:Class Doem implements Runnable,我们在同步代码块用锁的时候,只需要写“Doem.class”。