线程之间存在相互抢占,发生在代码的每一步,导致多线程的数据并发安全问题
解决方式—加锁
1.同步代码块锁
synchronized(锁对象){}
根据锁对象共享进来的线程对象保证在执行代码块内容时不会有抢占
锁对象:一个对象,可以把哪些线程对象共享进来
可以把当前参与的线程对象共享进来的对象
方法区资源(把所有的线程对象都共享)
this(当参与的所有线程对象被Runnable实现类对象共享)
package cn.tedu.thread;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class SellerTicketDemo1 {
public static void main(String[] args) throws IOException {
//加载配置文件
Properties p=new Properties();
p.load(new FileInputStream("ticket.properties"));
//根据键来获取值
String count=p.getProperty("count");
//创建代表票的类的对象
Ticket t=new Ticket();
//设置初始票数
t.setCount(Integer.parseInt(count));
//创建代表线程执行信息的类的对象---四个售票员
Seller s1=new Seller(t);
Seller s2=new Seller(t );
Seller s3=new Seller(t);
Seller s4=new Seller(t);
//创建四个线程对象(Thread)对象
Thread t1=new Thread(s1,"A");
Thread t2=new Thread(s2,"B");
Thread t3=new Thread(s3,"C");
Thread t4=new Thread(s4,"D");
//调用start方法
t1.start();
t2.start();
t3.start();
t4.start();
//主线程
}
}
//定义类---代表线程执行任务信息(卖票)
class Seller implements Runnable{
//代表票数---共卖100张票
//static int count=100;
//声明代表票的类的对象
private Ticket t;
//有参构造---保证创建的对象共享一个t对象
public Seller(Ticket t){
this.t=t;
}
//重写run方法---卖票过程
@Override
public void run() {
//循环实现
while (true) {
//同步代码块锁
//锁对象,给定兑现那个,可以共享哪些线程对象,被共享进来的线程对象
//被共享进来的线程对象,在代码块内就不会相互抢占
//synchronized (t) {//t---把当前参与的线程对象共享进来,
//指定锁对象可以把所有的线程对象共享进来----(方法去资源)
synchronized (String.class) {
// Seller.class String.class
//出循环条件
if (t.getCount() == 0) {
break;
}
//卖一张票---设置新的剩余票数
t.setCount(t.getCount() - 1);
//输出
//Thread.currentThread()---当前正在执行的线程对象
System.out.println(Thread.currentThread().getName()
+ "卖了一张票,还剩余" + t.getCount() + "张票...");
}
}
}
}
//代表票的类
class Ticket{
//代表票数
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
2.同步方法锁
根据锁对象共享进来的线程对象保证在执行方法里内容不会有抢占锁对象
当在非静态方法上加上锁,默认锁对象就是this
当在静态方法上加上锁,默认锁对象就是当前类。class(方法区资源)
package cn.tedu.thread;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class SellerTicketDemo2 {
public static void main(String[] args) throws IOException {
//加载配置文件
Properties p=new Properties();
p.load(new FileInputStream("ticket.properties"));
//根据键来获取值
String count=p.getProperty("count");
//创建代表票的类的对象
Ticket t=new Ticket();
//设置初始票数
t.setCount(Integer.parseInt(count));
//创建代表线程执行信息的类的对象---一个售票系统
Seller2 s1=new Seller2(t);
/*Seller s1=new Seller(t);
Seller s2=new Seller(t );
Seller s3=new Seller(t);
Seller s4=new Seller(t);*/
//创建四个线程对象(Thread)对象共享同一个Runnable对象
Thread t1=new Thread(s1,"A");
Thread t2=new Thread(s1,"B");
Thread t3=new Thread(s1,"C");
Thread t4=new Thread(s1,"D");
//调用start方法
t1.start();
t2.start();
t3.start();
t4.start();
//主线程
}
}
//定义类---代表线程执行任务信息(卖票)
class Seller2 implements Runnable{
//代表票数---共卖100张票
//static int count=100;
//声明代表票的类的对象
private Ticket t;
private static Object obj = new Object();
//有参构造---保证创建的对象共享一个t对象
public Seller2(Ticket t){
this.t=t;
}
//重写run方法---卖票过程
//同步方法锁
//如果是非静态方法,默认锁对象就是this
//如果是静态方法,默认锁对象就是当前类.class(方法去资源)
@Override
public synchronized void run() {
//循环实现
while (true) {
//同步代码块锁
//锁对象,给定兑现那个,可以共享哪些线程对象,被共享进来的线程对象
//被共享进来的线程对象,在代码块内就不会相互抢占
//synchronized (t) {//t---把当前参与的线程对象共享进来,
//指定锁对象可以把所有的线程对象共享进来----(方法去资源)
//synchronized (String.class) {
// Seller.class String.class
//this指代成一个Runnable实现类对象
//synchronized (obj) {
//出循环条件
if (t.getCount() == 0) {
break;
}
//卖一张票---设置新的剩余票数
t.setCount(t.getCount() - 1);
//输出
//Thread.currentThread()---当前正在执行的线程对象
System.out.println(Thread.currentThread().getName()
+ "卖了一张票,还剩余" + t.getCount() + "张票...");
//}
}
}
}
同步:在某个时刻只能由一个线程对象拥有资源(没抢占)
异步:一个资源会被多个线程对象来抢占
同步一定是安全的,异步不一定不安全
死锁
死锁由于锁的嵌套导致死锁问题,通过死锁检测来让其中一个线程对象先执行
package cn.tedu.thread;
public class DeadLockDemo {
//创建打印的扫描的对象
private static Scann s=new Scann();
private static Printer p=new Printer();
public static void main(String[] args) {
//员工---先打印再扫描
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
synchronized (p){
p.print();
//休眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s){
s.scan();
}
}
}
});
//员工---先扫描再打印
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
//
synchronized (s){
s.scan();
}
//休眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印
synchronized (p){
p.print();
}
}
});
//开启线程
t1.start();
t2.start();
}
}
//定义代表扫描的类
class Scann{
public void scan(){
System.out.println("呼哧呼哧的扫描...");
}
}
//第一代表打印的类
class Printer{
public void print(){
System.out.println("在呼哧呼哧的打印...");
}
}
等待唤醒机制(结合锁来使用)
通过wait()、notify()、notifyAll()以及标值位来控制线程对象执行顺序
package cn.tedu.thread;
public class WaitNotifyDemo {
public static void main(String[] args) {
//商品对象
Pruduct p=new Pruduct();
//创建线程对象
new Thread(new Pruductor(p)).start();//生产者
new Thread(new Consumer(p)).start();//消费者
}
}
//定义类---代表线程执行任务信息(生产者)
class Pruductor implements Runnable{
//声明商品类的对象
private Pruduct p;
//有参构造---保证共享同一个商品类的对象
public Pruductor(Pruduct p){
this.p=p;
}
@Override
public void run() {
//保证一直生产下去
while (true) {
synchronized (p) {
if (p.getFlag()==true)
//让线程对象进行等待
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//可以生产的最大值
int max = 1000 - p.getCount();
//随机生产的商品数量
int count = (int) (Math.random() * (max + 1));
//设置新的剩余的商品数量
p.setCount(p.getCount() + count);
//输出
System.out.println("生产" + count + "个商品,还剩" + p.getCount() + "商品...");
//唤醒等待线程对象(消费者)
p.notify();
//改变布尔值
p.setFlag(true);
}
}
}
}
//定义类----代表线程执行任务信息(消费者)
class Consumer implements Runnable{
//
private Pruduct p;
//有参构造---保证共享同一个商品类的对象
public Consumer(Pruduct p){
this.p=p;
}
//指定消费过程
@Override
public void run() {
//保证一直消费下去
while (true){
synchronized (p){
//
if (p.getFlag()==false)
//让线程对象进行等待
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//随机消费的商品数量
int count=(int)(Math.random()*(p.getCount()+1));
//设置新的剩余商品数量
p.setCount(p.getCount()-count);
//输出
System.out.println("消费了"+count+"个商品,还剩"+p.getCount()+"商品...");
//唤醒等待的线程对象
p.notify();
//改变布尔值
p.setFlag(false);
}
}
}
}
//代表商品的类
class Pruduct{
//属性---商品数量
private int count;
//标志位
private boolean flag;
public boolean getFlag(){
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
wait()与sleep()方法的区别
sleep():使用时一定要给定休眠时间,到点自然醒。在休眠期间会释放线程对象的CPU的执行权(无论有锁没锁都会释放执行权但是如果有锁不会释放锁对象)。定义在Thread类里的静态方法。
wait():使用时可以给定等待时间到点自然醒,但是如果没有指定等待时间需要强制进行唤醒。在等待期间释放线程对象的CPU执行权,释放锁对象。定义在Object类里。
线程的状态:
线程的优先级(一定程度上避免死锁的发生)
优先级从1到10,优先级级别逐渐增大。理论上优先级越大抢占到资源的概率就越大。
理论上优先级之差大于5那么抢占到的资源的概率会稍微大一点
package cn.tedu.thread;
public class PriorityDemo {
public static void main(String[] args) {
//创建线程对象
Thread t1=new Thread(new PDemo(),"A");
Thread t2=new Thread(new PDemo(),"B");
//设置优先级
t1.setPriority(1);
t2.setPriority(10);
//开启线程
t1.start();
t2.start();
}
}
//代表线程执行信息的类
class PDemo implements Runnable{
@Override
public void run() {
for(int i=0;i<=20;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
//休眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
守护线程
当被守护线程执行结束所有的守护线程随之结束
当被守护线程出现多个时,所有的被守护线程结束,守护线程随之结束。(类加载器—守护线程)
package cn.tedu.thread;
public class DaemonDemo {
public static void main(String[] args) throws InterruptedException {
//创建线程对象---四个小兵
Thread t1=new Thread(new Soldier(),"宫本");
Thread t2=new Thread(new Soldier(),"亚索");
Thread t3=new Thread(new Soldier(),"劫");
Thread t4=new Thread(new Soldier(),"提莫");
//设置守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t3.setDaemon(true);
t4.setDaemon(true);
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
//BOSS---主线程---被守护线程
for(int i=20;i>=0;i--){
System.out.println("BOSS掉了一滴血,还剩"+i+"滴血...");
//休眠
Thread.sleep(5);
}
}
}
///代表小兵的类
class Soldier implements Runnable{
@Override
public void run() {
for(int i=10;i>=0;i--){
//
System.out.println(Thread.currentThread().getName()
+"掉了一滴血,还剩"+i+"滴血...");
//休眠
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
网络编程(套接字)
基于网络进行数据传输(IO流)
物理层、数据链路层、传输层(UDP、TCP)、会话层、表示层、应用层
IP地址:主机在网络中的位置 IPv4是由0~255的数字组成
域名解析器:把域名解析成对应的IP地址
端口:让计算机和外界进行数据交互的媒介 端口号(065538)从01024端口号基本都被系统占用了,使用时从后面开始使用