笼统的介绍我了解的多线程:首先Java中的多线程是抢占式(线程的调度策略),并发执行(在同一个时间段内执行)的。
一、线程的状态
在jdk的API中线程的状态有如下六种:

二、两种常用的创建线程的方式
????????NEW(新的,在Java中创建对象时的关键字,可以理解为是编程语言中的创建创造),线程处于这一状态,无非就是我们程序员对线程的编写。
????????创建线程的方式有三种:继承Thread类、实现Runnable接口、使用Callable和Future创建线程。
方式一:继承Thread类( extends Thread),并重写run()方法
public class ThreadTest extends Thread{
private int i;
//重写run()方法,run()方法的方法体就是线程的执行体
public void run() {
for(;i<100;i++) {
//当线程继承Thread类时直接使用this即可获取当前进程
//Thread对象的getName()方法返回当前线程的名字
//因此可以直接调用getName()方法返回当前线程的名字
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) {
//main方法其实也是一个线程,更是线程中的主线程
for(int i=0;i<100;i++) {
//调用Thread的currentThead()方法获取当前线程
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
//创建并启动第一个线程
//start()启动该线程的run()方法即线程执行体
new ThreadTest().start();
//创建并启动第二个线程
new ThreadTest().start();
}
}
}
}
?
方式二: 实现Runnable接口,并重写run()方法
实现Runnable的形式创建线程:
1--创建一个任务对象。实现Runnable,重写run()方法
2--创建一个线程,并为其分配一个任务
3--执行这个线程
实现Runnable 与 集成Thread相比存在的优势:
1--通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
2--可以避免单线程继承带来的局限性。(可以实现多个接口,可以继承一个父类)
3--任务与线程本身分离,提高了程序的健壮性
4--后续学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
?
public class RunnableTest implements Runnable{
private int i;
//run()方法 同样是线程的执行体
public void run() {
/*
* 用于给线程执行的任务
*/
for(;i<100;i++) {
//当前线程实现接口时
//如果获取当前线程,只能用Thread。currentThread()方法
//这里是实现Runnable接口,而不是继承Thread,所以要用Thread。currentThread()方法获取当前的线程
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=0;i<100;i++) {
//调用Thread的currentThead()方法获取当前线程
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
RunnableTest st=new RunnableTest();
//创建并启动第一个线程
//start()启动该线程的run()方法即线程执行体
//Thread的作用是把run()方法包装成线程的执行体
new Thread(st,"新线程1").start();
//床架并启动第二个线程
new Thread(st,"新线程2").start();
}
}
}
}
?
主线程的线程名需要获取主线程,在获得当前线程的名字的形式得到:
Thread.currentThread():获取当前线程,当线程继承Thread类时,在run()方法中直接使用this即可获取当前进程(this在类内可以省略)。但是除此之外的情况:在主线程中获取当前线程、在Runnable的实现类的任务方法中,必须使用Thread.currentThread()方法。
getName():获取线程的名字,在获取当线程后可以通过此方法获得当前线程的名字。
三、线程的阻塞和中断
线程阻塞(耗时操作):所有消耗时间的操作
例如:读取文件,导致线程等待,除非文件读取完毕才能继续执行该线程
线程中断:一个线程是一个独立执行的路径,他是否结束,应该由其自身决定。
线程分为守护线程和用户线程
用户线程:当一个进程不包含任何的存活的用户线程(直接创建的线程都是用户线程,及主线程)时。进行结束
守护线程:守护用户的线程,当最后一个用户线程结束时,所有守护线程自动死亡。设置守护线程:线程对象调用setDaemon(true)方法,true参数为开启守护线程--要在线程启动之前设置。
public class Interrupt {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t = new Thread(new MyRunnable());
// //设置守护进程--要在线程启动之前设置
// t.setDaemon(true);
t.start();
for(int i = 0;i <= 10;i++) {
System.out.println(Thread.currentThread().getName()+""+i);
try {
Thread.sleep(1000);//等待1秒钟
}catch(InterruptedException e) {
e.printStackTrace();
}
}
//给线程t添加中断标记--在特殊操作室检查中断标记,发现时抛出异常进入catch块
/*
* 打标记后知识进入catch块而已,是否真正的死亡,看程序员在catch块的操作
* 不进行死亡操作,catch块的内容为空
* 死亡操作:直接return;
*/
t.interrupt();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i <= 10;i++) {
System.out.println(Thread.currentThread().getName()+""+i);
try {
Thread.sleep(1000);//等待1秒钟
}catch(InterruptedException e) {
//e.printStackTrace();
System.out.println("发现了中断标记,线程自杀");
return;
}
}
}
}
}
四、线程锁
在了解线程锁之前,先了解线程安全问题:多个线程同时运行同一个任务时,容易发生线程不安全的问题。
下面的代码模拟多个线程访问一个任务致使在数据访问时发生异常
public class NoSecurity {
public static void main(String[] args) {
// TODO Auto-generated method stub
Runnable run = new Ticket();
Thread t1 = new Thread(run,"线程1");
t1.start();
Thread t2 = new Thread(run,"线程2");
t2.start();
Thread t3 = new Thread(run,"线程3");
t3.start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
@Override
public void run() {
//卖票
while(count > 0) {//循环使得一个线程的票全部卖完为止
System.out.println(Thread.currentThread().getName()+"正在准备卖票");
//该线程在准备秒票钱进入睡眠使其他线程运行,导致该线程数据出错
/*
* 因为其他线程的介入,导致count进入循环前的数据跟count--的数据不一致
* (三个线程同时进入循环,因为时间篇的丢失,count--运算的数据不正常)
*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"卖票成功,余票" + count);
}
}
}
}
?解决方案:使用线程锁,在Java中有三种锁的创建形式。按照分类可以分为:
显示锁:同步锁(Lock)
隐式锁:同步代码块,同步方法
同步代码块:
**
* 解决线程不安全问题:
* 在线程执行任务的数据运算之前,不允许其他线程的插足、介入----排队执行
* 实现线程的同步--加锁
*
*
* 解决方案1--同步监视器的通用方法“同步代码块”(简单认为括住的内容是会排队的)
* 在数据之前进行上锁
*
* 同步代码块的对象只能是引用数据类型
* 多个代码线程使用同一把锁--所以锁对象的创建应该在任务之前
*
* @author jiazikang
*
*线程1先执行的抢到的几率更高
*/
public class Solve_Synchronized {
public static void main(String[] args) {
Runnable run = new Ticket();
Thread t1 = new Thread(run,"线程1---");
t1.start();
Thread t2 = new Thread(run,"线程2---");
t2.start();
Thread t3 = new Thread(run,"线程3---");
t3.start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
private Object o = new Object();
@Override
public void run() {
//卖票
while (true) {
synchronized (o) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "卖票成功,余票" + count);
}else {
break;
}
}
}
}
}
}
?主要操作步骤:1.创建锁对象private Object o = new Object();
? ? ? ? ? ? ? ? ? ? ? ? ? ?2.使用synchronized (o) {}形式的代码块
同步方法?:
/**
* 解决方案2同步方法
* 同步方法:
* 编写一个以synchronized关键字作为修饰符的方法,在线程的任务中调用该方法
*
* 锁为:调用该方法(任务方法)的this、
* 如果同步方法为静态的话,调用该方法的为:类名.class
* @author jiazikang
*
*/
public class Solve_SynchronizedWay {
public static void main(String[] args) {
Runnable run = new Ticket();
Thread t1 = new Thread(run,"线程1---");
t1.start();
Thread t2 = new Thread(run,"线程2---");
t2.start();
Thread t3 = new Thread(run,"线程3---");
t3.start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
private Object o = new Object();
private synchronized boolean sale() {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "卖票成功,余票" + count);
return true;
}
return false;
}
@Override
public void run() {
//卖票
while (true) {
Boolean flag = sale();
if(!flag) {
break;
}
}
}
}
}
?主要操作步骤:编写使用synchronized关键字修饰的方法,对想要加锁的内容进行封装
同步锁?:
主要操作步骤:
1.定义锁对象
private final ReentrantLock lock = new ReentrantLock();
2.调用对象的加锁方法:lock.lock();
3.调用对象的开锁方法:lock.unlock();
代码与上面内容基本相同,加锁内容的方法或者代码块的执行体开始部分使用lock.lock();执行体的要结束地方使用lock.unlock();
五、线程通信
实质:多个线程之间的交流
大致思路:
1.创建多个线程
2.创建一个当前线程操作结束的标识,根据等待和唤醒操作为标识设置相应的值
等待:wait();? ? ? ? ? ? ? ?唤醒:notifyAll();
3.在一个锁内完成交流操作(保证线程的安全和数据的正确)
public class ThreadCommunication {
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndSaste("老干妈小米粥","香辣味");
}else{
f.setNameAndSaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
//true 表示可以生产
private boolean flag = true;
public synchronized void setNameAndSaste(String name,String taste){
if(flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag) {
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
?
|