多线程:就是应用程序多条执行路径
进程:正在运行的程序,
线程: 进程的执行单元,一条执行路径
线程的简单使用方式一:
package thread;
/**
*
* @author Angus
* getName() 返回该线程的名称。
*/
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(getName()+"hello");
}
}
}
ThreadDemo测试;
package thread;
/**
* @author Angus
*
* 多线程:就是应用程序多条执行路径
* 进程:正在运行的程序,
* 线程: 进程的执行单元,一条执行路径
*
* 实现?
* 线程类Thread:
* A;定义一个类基础Thread类,重写run方法,start()方法启动线程。
* B:实现Runnable接口,重写run方法
*
*/
public class ThreadDemo{
public static void main(String[] args) {
MyThread mt = new MyThread();
MyThread mt2 = new MyThread();
mt.start();
// mt.start(); //Exception in thread "main" java.lang.IllegalThreadStateException
// //连续开始两次线程报错。。分析异常开启两个线程需要两个线程对象
mt.setName("zzz");
mt2.setName("ddd");
mt2.start();
}
}
线程的简单使用方式二;
package thread;
/**
*
* @author Angus
* B:实现Runnable接口,重写run方法
*/
public class MyRunnableDemo {
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
//数显runnable的发类没有start的方法,启动必须启动start方法
//所以需要考虑Thread类
Thread t = new Thread(my);
Thread t2 = new Thread(my);
t.setName("xxxx");
t2.setName("wwww");
t.start();
t2.start();
}
}
MyRunnable类:
package thread;
/**
* @author Angus 实现Runnable接口,重写run方法 public static Thread currentThread()
* 返回对当前正在执行的线程对象的引用。
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(Thread.currentThread().getName() + " hello");
}
}
}
运行结果差不多。。。。
会发现第一种每次需要new两个对象,创建两个线程,走了两次run,第二种只创建了一个资源对象,这样数据和操作分离更好一些,一般开发中使用第二种多一些。。。
线程的生命周期:
多线程应用的场景:
运行都会卡死,建议DOS测试,这样可以关闭,eclipse只能关闭软件了。。。
方式一:
package thread2;
/**
*
* @author Angus
* 多线程应用的场景
* 窗口卖票
*
* 有一趟火车,票不多了,还有200张,四个窗口卖票
*
* 使用多线程模拟卖票
* 方式1 继承Thread类
* 方式2实现Runnable接口
*
*/
public class ThreadTest {
public static void main(String[] args) {
TicketTread tt1 = new TicketTread();
TicketTread tt2 = new TicketTread();
TicketTread tt3 = new TicketTread();
TicketTread tt4 = new TicketTread();
tt1.setName("窗口1");
tt2.setName("窗口2");
tt3.setName("窗口3");
tt4.setName("窗口4");
tt1.start();
tt2.start();
tt3.start();
tt4.start();
}
}
TicketTread
package thread2;
/**
* @author Angus
* 方式一
*/
public class TicketTread extends Thread {
@Override
public void run() {
int tickets = 200;
while(true){
if(tickets>0){
System.out.println(getName()+" 正在出售"+(tickets--)+"张票");
}
}
}
}
这样运行会CPU报表。。。直接死机,
原因: 每个线程new的时候都是一个新的200,都走run方法,,死循环。。
改进;
TicketTread
package thread2;
/**
* @author Angus
* 方式一
*/
public class TicketTread extends Thread {
private static int tickets = 200;
@Override
public void run() {
while(true){
if(tickets>0){
System.out.println(getName()+" 正在出售"+(tickets--)+"张票");
}
}
}
}
而static修饰的成员变量比较占用资源,去掉更换第二种方式测试
package thread2;
/**
*
* @author Angus
* 方式二
*/
public class TicketRunnanble implements Runnable {
private int tickets = 200;
@Override
public void run() {
while(true){
if(tickets>0){
System.out.println(Thread.currentThread().getName()+" 正在出售"+(tickets--)+"张票");
}
}
}
}
Runnable测试:
package thread2;
/**
*
* @author Angus
* 方式二
*/
public class RunnableTest {
public static void main(String[] args) {
TicketRunnanble tr = new TicketRunnanble();
Thread t1 = new Thread(tr);
Thread t2 = new Thread(tr);
Thread t3 = new Thread(tr);
Thread t4 = new Thread(tr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t4.setName("窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行发现也会出现卡死的现象,和之前一样出现一样的票卖了多次的情况。
加一个睡眠操作:
package thread2;
/**
*
* @author Angus
* 方式二
*/
public class TicketRunnanble implements Runnable {
private int tickets = 200;
@Override
public void run() {
while(true){
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 正在出售"+(tickets--)+"张票");
}
}
}
}
会发现出现了负数:
甚至还有-2
当剩余最后一张票的时候,线程判断 >0 ,四个线程都进来了,这样就会出现负数的情况在 t--的情况下。而且在ticket--的情况下 也会出现相同票数的情况。。
解决:引入线程锁 synchronized
package thread2;
/**
*
* @author Angus
* 解决出现负数和相同数的方法;
* 在出问题的代码中加一个锁 找问题代码:
* 1 找共享数据
* 2 共享数据中有没有多条语句
* 3是不是在多线程中
* 怎么锁?
* synchronized(锁对象)
* 注意;多个线程必须使用同一把锁
*/
public class TicketRunnanble implements Runnable {
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 正在出售" + (tickets--) + "张票");
}
}
}
}
}
这样运行就没有问题了,不会出现负数和相同的数据了。
方法锁的引入:
package thread2;
/**
*
* @author Angus synchronized(锁对象)
*
* 锁对象除了object还可以试其它对象? 结论; 同步代码锁可以试任意对象
*/
class Demo {
// 测试发现没问题。
}
public class TicketRunnanble implements Runnable {
private int tickets = 100;
private Object obj = new Object();
private Demo d = new Demo();
@Override
public void run() {
int x = 0;
while (true) {
if (x % 2 == 0) {
synchronized (d) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 正在出售" + (tickets--) + "张票");
}
}
} else {
synchronized (d) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 正在出售" + (tickets--) + "张票");
}
}
}
x++;
}
}
}
测试没有问题,同一个锁对象。。。把else抽取成方法测试;
package thread2;
/**
*
* @author Angus synchronized(锁对象)
*
* 锁对象除了object还可以试其它对象? 结论; 同步代码锁可以试任意对象
*/
class Demo {
// 测试发现没问题。
}
public class TicketRunnanble implements Runnable {
private int tickets = 100;
private Object obj = new Object();
private Demo d = new Demo();
@Override
public void run() {
int x = 0;
while (true) {
if (x % 2 == 0) {
synchronized (d) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 正在出售" + (tickets--) + "张票");
}
}
} else {
check();
}
x++;
}
}
private void check() {
synchronized (d) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 正在出售" + (tickets--) + "张票");
}
}
}
}
这样运行也没有问题,OK,线程抽取方法锁测试;
private synchronized void check() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 正在出售" + (tickets--) + "张票");
}
}
方法中加了锁,代码中就不需要加了,测试。。:
出现了一样的,因为方法锁也没有确认同一个对象,if中用的是demo对象。。
问题一:测试返现同步方法锁的对象为this对象修改代码:
synchronized (this)
这样就和同步方法为同一个对象,线程就没有问题了。。
问题二:静态方法锁的对象是?
测试返现为:
是当前类的字节码文件对象
类名.class
修改代码:
synchronized (TicketRunnanble.class)
线程的死锁:
MyLock
package thread2;
public class MyLock {
public static final Object obja = new Object();
public static final Object objb = new Object();
}
DieLock
package thread2;
/**
* @author Angus
*
* 死锁的问题:
* 五个人吃饭,菜齐了,一人只有一只筷子,每个人思考,把筷子借给别人,然后别人吃完自己再吃,
* 假如五个人都饿了,都拿着自己的筷子,并且等着别人放下筷子,然后使用,但是没有一个人愿意先放下筷子
* 所以就出现了死锁。。
*
*/
public class DieLock extends Thread{
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if(flag){
synchronized (MyLock.obja) {
System.out.println("true---obja");
//当线程d1走到这里线程2抢到了起源 线程d1需要 MyLock.objb
synchronized (MyLock.objb) {
System.out.println("true---objb");
}
}
}else{
synchronized (MyLock.objb) {
System.out.println("true---objb");
//当线程d2走到这里的时候,线程d2需要 MyLock.obja
synchronized (MyLock.obja) {
System.out.println("true---obja");
}
}
}
//谁也不放锁,就会造成死锁。。。。。
}
}
测试DieLockDemo
package thread2;
public class DieLockDemo {
public static void main(String[] args) {
DieLock d1 = new DieLock(true);
DieLock d2 = new DieLock(false);
d1.start();
d2.start();
}
}
结果; 出现了死锁。。。。
线程间的通讯:
线程间的通讯:不同种类的线程对共享数据的操作功能。。。
* 举例:以学生做为资源
* 学生类
* 设置学生类
* 获取学生类
* 测试类
Student
package thread3;
public class Student {
String name;
int age;
}
SetStudent
package thread3;
public class SetStudent implements Runnable {
private Student s;
public SetStudent(Student s) {
this.s = s;
}
@Override
public void run() {
int x = 0;
while(true){
if(x %2 ==0){
s.name = "呵呵1";
s.age = 28;
}else{
s.name = "哈哈2";
s.age = 11;
}
}
}
}
GetStudent
package thread3;
public class GetStudent implements Runnable{
private Student s;
public GetStudent(Student s) {
this.s = s;
}
@Override
public void run() {
while(true){
System.out.println(s.name+s.age);
}
}
}
StudentTest
package thread3;
/**
* @author Angus
* 线程间的通讯:
* 举例:以学生做为资源
* 学生类
* 设置学生类
* 获取学生类
* 测试类
*
*测试发现,出现年龄和姓名的交叉问题。。
*/
public class StudentTest {
public static void main(String[] args) {
Student s = new Student();
SetStudent ss = new SetStudent(s);
GetStudent gs = new GetStudent(s);
Thread t1 = new Thread(ss);
Thread t2 = new Thread(gs);
t1.start();
t2.start();
}
}
结果:
测试发现,出现年龄和姓名的交叉问题。。在run方法下,线程的随机性,出现了这样的情况。。
需要同步锁解决。
@Override
public void run() {
//t1进来
int x = 0;
while(true){
synchronized (s) {
if(x %2 ==0){
s.name = "呵呵 ";
s.age = 28;
}else{
s.name = "哈哈 ";
s.age = 11;
}
}
x++; //x=1
}
}
@Override
public void run() {
while(true){
synchronized (s) {
System.out.println(s.name+s.age);
}
}
}
运行解决。。。
但是发现在控制台一打一大片一个线程的数据,能不能每个线程交叉进程呢?
线程的等待:
* 发现输出的内容是一大片一大片输出的,能不能一个输出后再输出另一个交叉进行呢?
*
* 针对输出:
* 判断是否有数据,有就输出,没有待设置数据
* 针对设置:
* 判断是否有数据,有就等待输出,否则输出
* 等待唤醒机制;
* wait(); //等待
* notify();//唤醒
修改代码:
Student;
package thread3;
public class Student {
String name;
int age;
boolean flag = false; //ture 有数据
}
SetStudent
@Override
public void run() {
//t1进来
int x = 0;
while(true){
synchronized (s) {
//如果有数据等待
if(s.flag){
try {
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(x %2 ==0){
s.name = "呵呵 ";
s.age = 28;
}else{
s.name = "哈哈 ";
s.age = 11;
}
//修改标记
s.flag = true;
s.notify();
}
x++; //x=1
}
}
GetStudent
@Override
public void run() {
while(true){
synchronized (s) {
if(!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(s.name+s.age);
s.flag = false;
s.notify();
}
}
}
运行结果;全部交叉进行。。。。
线程的优先级;
PriorityDemo
package thread4;
public class PriorityDemo implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" ----"+i);
}
}
}
PriorityTset
package thread4;
/**
* 线程的优先级
* @author Angus
*
* getPriority() 返回线程的优先级。
* setPriority(int newPriority) 更改线程的优先级。
*
* 运行发现,优先级并不是每次都最先运行
* 注意:优先级只是在一定的程度上,让线程优先级的多执行次数
*/
public class PriorityTset {
public static void main(String[] args) {
PriorityDemo pd = new PriorityDemo();
Thread t1 = new Thread(pd);
Thread t2 = new Thread(pd);
Thread t3 = new Thread(pd);
t1.setName("哈哈1");
t2.setName("哈哈2");
t3.setName("哈哈3");
t1.setPriority(1); //默认5范围1-10
t2.setPriority(5);
t3.setPriority(10);
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println(t3.getPriority());
t1.start();
t2.start();
t3.start();
}
}
其它API中的方法:
public static void yield()暂停当前正在执行的线程对象,并执行其他线程。
让线程更和谐一些,一般不怎么用。。。。wait线程唤醒可以替换。。
public final void join()
throws InterruptedException等待该线程终止。
也是加入线程,可以让一个线程加入到运行中的线程中。。。。。前提是线程启动start了。。
public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
最后附上JDK使用文档API 下载